diff --git a/.gitignore b/.gitignore index 643aef56b..46e6231d1 100644 --- a/.gitignore +++ b/.gitignore @@ -181,6 +181,22 @@ backups/ # Backup de gamilit (submodule migration) projects/gamilit.bak.*/ +# ----------------------------------------------------------------------------- +# MCP SERVERS - Repositorios independientes +# ----------------------------------------------------------------------------- +# Los MCP servers internos son repositorios independientes que se clonan +# manualmente despues de clonar workspace-v1. +# Ver core/mcp-servers/_registry.yml para lista completa e instrucciones. +# ----------------------------------------------------------------------------- +core/mcp-servers/internal/rag-knowledge/ +core/mcp-servers/internal/scrum-taiga/ +# Mantener estructura base (README, registry, templates) +!core/mcp-servers/README.md +!core/mcp-servers/_registry.yml +!core/mcp-servers/internal/.gitkeep +!core/mcp-servers/external/ +!core/mcp-servers/templates/ + # ----------------------------------------------------------------------------- # SUBREPOSITORIOS - Proyectos con repositorios independientes en Gitea # ----------------------------------------------------------------------------- @@ -189,6 +205,7 @@ projects/gamilit.bak.*/ # # NOTA: gamilit NO se ignora porque es un submodulo Git (ver .gitmodules) # ----------------------------------------------------------------------------- +# ERP Family projects/erp-suite/ projects/erp-core/ projects/erp-construccion/ @@ -196,7 +213,18 @@ projects/erp-clinicas/ projects/erp-retail/ projects/erp-mecanicas-diesel/ projects/erp-vidrio-templado/ + +# Trading & Analytics projects/trading-platform/ projects/betting-analytics/ projects/inmobiliaria-analytics/ projects/platform_marketing_content/ + +# Nuevos proyectos (2026-01-07) +projects/michangarrito/ +projects/template-saas/ +projects/clinica-dental/ +projects/clinica-veterinaria/ + +# Gitea token (no commitear) +.gitea-token diff --git a/SUBREPOSITORIOS.md b/SUBREPOSITORIOS.md index 1fd3c0c95..19f480e09 100644 --- a/SUBREPOSITORIOS.md +++ b/SUBREPOSITORIOS.md @@ -1,7 +1,7 @@ # Subrepositorios del Workspace -**Fecha:** 2025-01-04 -**Version:** 1.1 +**Fecha:** 2026-01-07 +**Version:** 1.3 --- @@ -97,6 +97,15 @@ Estos proyectos SI pueden tener subrepositorios para sus apps (backend, frontend | **inmobiliaria-analytics** | `projects/inmobiliaria-analytics` | `http://72.60.226.4:3000/rckrdmrd/inmobiliaria-analytics.git` | | **platform_marketing_content** | `projects/platform_marketing_content` | `http://72.60.226.4:3000/rckrdmrd/platform_marketing_content.git` | +### Proyectos Nuevos (2026-01-07) + +| Proyecto | Path Local | Repositorio | Subrepositorios | +|----------|------------|-------------|-----------------| +| **michangarrito** | `projects/michangarrito` | `http://72.60.226.4:3000/rckrdmrd/michangarrito.git` | backend, frontend, mobile, database, mcp-server, whatsapp | +| **template-saas** | `projects/template-saas` | `http://72.60.226.4:3000/rckrdmrd/template-saas.git` | backend, frontend, database | +| **clinica-dental** | `projects/clinica-dental` | `http://72.60.226.4:3000/rckrdmrd/clinica-dental.git` | database | +| **clinica-veterinaria** | `projects/clinica-veterinaria` | `http://72.60.226.4:3000/rckrdmrd/clinica-veterinaria.git` | database | + ### Estructura con Subrepositorios (para proyectos Gitea) Los proyectos en Gitea pueden usar esta estructura de subrepositorios: @@ -207,4 +216,93 @@ git -C /home/isem/workspace-v1/projects/gamilit status --- +## Estado Actual de Repositorios en Gitea + +### Repositorios Existentes (2026-01-04) + +| Repositorio | Tipo | Estado | +|-------------|------|--------| +| workspace | Principal | Activo | +| workspace-v1 | Principal | Activo | +| erp-construccion-backend | Subrepositorio | Activo | +| erp-construccion-frontend-web | Subrepositorio | Activo | +| erp-construccion-frontend-mobile | Subrepositorio | Activo | +| erp-construccion-database | Subrepositorio | Activo | +| erp-mecanicas-diesel-backend | Subrepositorio | Activo | +| erp-mecanicas-diesel-frontend-web | Subrepositorio | Activo | +| erp-mecanicas-diesel-database | Subrepositorio | Activo | +| erp-core-backend | Subrepositorio | Activo | +| erp-core-frontend-web | Subrepositorio | Activo | +| erp-core-database | Subrepositorio | Activo | + +### Repositorios Creados (2026-01-07) + +Los siguientes repositorios fueron creados via API: + +| Repositorio | Tipo | Estado | +|-------------|------|--------| +| michangarrito | Principal | ✅ Creado | +| michangarrito-backend | Subrepositorio | ✅ Creado | +| michangarrito-frontend | Subrepositorio | ✅ Creado | +| michangarrito-mobile | Subrepositorio | ✅ Creado | +| michangarrito-database | Subrepositorio | ✅ Creado | +| michangarrito-mcp-server | Subrepositorio | ✅ Creado | +| michangarrito-whatsapp | Subrepositorio | ✅ Creado | +| template-saas | Principal | ✅ Creado | +| template-saas-backend | Subrepositorio | ✅ Creado | +| template-saas-frontend | Subrepositorio | ✅ Creado | +| template-saas-database | Subrepositorio | ✅ Creado | +| clinica-dental | Principal | ✅ Creado | +| clinica-dental-database | Subrepositorio | ✅ Creado | +| clinica-veterinaria | Principal | ✅ Creado | +| clinica-veterinaria-database | Subrepositorio | ✅ Creado | + +### Repositorios Pendientes de Crear + +Los siguientes repositorios principales necesitan crearse via API o web de Gitea: + +- erp-suite (principal) +- erp-core (principal) +- erp-construccion (principal) +- erp-clinicas (principal) +- erp-retail (principal) +- erp-mecanicas-diesel (principal) +- erp-vidrio-templado (principal) +- trading-platform (principal) +- betting-analytics (principal) +- inmobiliaria-analytics (principal) +- platform-marketing-content (principal) + +--- + +## Scripts de Gestion + +### Crear repositorios en Gitea + +```bash +# Requiere token de API de Gitea +./scripts/create-gitea-repos-api.sh + +# Para obtener el token: +# 1. Ir a http://72.60.226.4:3000/rckrdmrd +# 2. Settings -> Applications -> Generate New Token +# 3. Dar permisos de 'repo' y 'write:repository' +``` + +### Push de todos los proyectos + +```bash +# Despues de crear los repositorios +./scripts/push-all-projects.sh +``` + +### Configurar repositorios locales + +```bash +# Configura remotes en cada proyecto +./scripts/create-gitea-repos.sh +``` + +--- + *Generado por NEXUS v3.4 - Sistema de Orquestacion* diff --git a/control-plane/manifests/repos.manifest.yml b/control-plane/manifests/repos.manifest.yml index cae94e820..19bed2304 100644 --- a/control-plane/manifests/repos.manifest.yml +++ b/control-plane/manifests/repos.manifest.yml @@ -155,7 +155,7 @@ repositories: shared-libs: type: "shared" description: "Librerias compartidas" - path: "shared/libs/" + path: "shared/catalog/" status: "planned" packages: - "@workspace/auth" diff --git a/core/README.md b/core/README.md index 6e3a9e49f..ace969d61 100644 --- a/core/README.md +++ b/core/README.md @@ -1,119 +1,127 @@ -# Core - Núcleo de la Fábrica de Software +# Core - Arquitectura del Workspace -## Descripción +**Version:** 2.0.0 +**Actualizado:** 2026-01-04 -El directorio `core/` contiene todo lo que se comparte a nivel de **fábrica**, no de proyecto individual: +## Descripcion -- Sistema de orquestación de agentes -- Módulos de código reutilizables -- Estándares técnicos y de negocio -- Directivas globales para agentes/subagentes -- Constantes y tipos universales +El directorio `core/` contiene la **arquitectura central del workspace**: sistema de orquestacion, MCP servers, y herramientas de ambiente. NO contiene codigo de aplicacion ni recursos compartidos (esos estan en `shared/`). ## Estructura ``` core/ -├── modules/ # Código compartido ejecutable -│ ├── utils/ # Utilidades universales ✅ -│ │ ├── date.util.ts # Manipulación de fechas -│ │ ├── string.util.ts # Manipulación de strings -│ │ ├── validation.util.ts # Validaciones -│ │ └── index.ts -│ ├── auth/ # Autenticación (por implementar) -│ ├── billing/ # Facturación -│ ├── notifications/ # Notificaciones -│ ├── payments/ # Pagos -│ └── multitenant/ # Multi-tenancy +├── README.md # Este archivo │ -├── constants/ # Constantes globales ✅ -│ ├── enums.constants.ts # Enums universales -│ ├── regex.constants.ts # Patrones regex -│ └── index.ts +├── mcp-servers/ # MCP Servers para el workspace +│ ├── internal/ # Servidores MCP internos +│ ├── external/ # Referencias a servidores externos +│ ├── templates/ # Templates para crear nuevos MCP servers +│ └── _registry.yml # Registro de servidores disponibles │ -├── types/ # Tipos TypeScript compartidos ✅ -│ ├── api.types.ts # Tipos de API -│ ├── common.types.ts # Tipos comunes -│ └── index.ts +├── orchestration/ # Sistema NEXUS/SIMCO de orquestacion +│ ├── agents/ # Perfiles de agentes +│ │ ├── perfiles/ # Perfiles ligeros SIMCO +│ │ └── legacy/ # Prompts legacy (referencia) +│ ├── directivas/ # Directivas por operacion +│ │ ├── simco/ # Sistema SIMCO +│ │ ├── legacy/ # Directivas legacy +│ │ └── _MAP.md +│ ├── referencias/ # ALIASES.yml y referencias +│ ├── templates/ # Templates de documentacion +│ ├── auditorias/ # Auditorias arquitectonicas +│ ├── impactos/ # Matrices de impacto +│ ├── inventarios/ # Inventarios de deployment +│ ├── procesos/ # Guias de procesos +│ ├── deployment/ # Arquitectura de deployment +│ ├── claude/ # Configuraciones Claude +│ └── _historico/ # Documentos archivados │ -├── catalog/ # Documentación de funcionalidades -│ ├── auth/ -│ ├── notifications/ -│ └── ... -│ -├── orchestration/ # Sistema de agentes NEXUS -│ ├── agents/ -│ ├── directivas/ -│ ├── templates/ -│ └── referencias/ -│ -└── standards/ # Estándares técnicos globales - ├── CODING-STANDARDS.md - ├── TESTING-STANDARDS.md - └── ... +└── devtools/ # Herramientas de ambiente + └── environment/ # Configuracion de ambientes + ├── scripts/ # Scripts de setup + ├── templates/ # Templates .env + └── DEV-ENVIRONMENT-REGISTRY.yml ``` +## Que NO esta en core/ + +Los siguientes elementos fueron movidos a `shared/`: + +| Elemento | Nueva ubicacion | +|----------|-----------------| +| catalog/ | `shared/catalog/` | +| modules/ | `shared/modules/` | +| constants/ | `shared/constants/` | +| types/ | `shared/types/` | +| standards/ | `shared/knowledge-base/standards/` | + ## Uso -### Importar Utilidades +### MCP Servers -```typescript -// En cualquier proyecto del workspace -import { formatDate, slugify, isEmail } from '@core/modules/utils'; +```bash +# Ver servidores disponibles +cat core/mcp-servers/_registry.yml -// O importar específico -import { formatToISO, addDays } from '@core/modules/utils/date.util'; +# Crear nuevo servidor MCP interno +cp -r core/mcp-servers/templates/TEMPLATE-MCP-INTERNO core/mcp-servers/internal/mi-servidor ``` -### Importar Constantes +### Sistema de Orquestacion -```typescript -import { UserStatus, PaymentStatus } from '@core/constants'; -import { EMAIL_REGEX, UUID_REGEX } from '@core/constants/regex.constants'; +Los agentes cargan automaticamente las directivas de `core/orchestration/directivas/` al inicializar. + +```markdown +# Inicializacion de agente +1. Leer core/orchestration/directivas/simco/_INDEX.md +2. Leer core/orchestration/directivas/principios/*.md +3. Cargar perfil desde core/orchestration/agents/perfiles/ +4. Leer ALIASES.yml para navegacion ``` -### Importar Tipos +### Herramientas de Ambiente -```typescript -import { ApiResponse, PaginatedResponse } from '@core/types'; -import { BaseEntity, Address } from '@core/types/common.types'; +```bash +# Validar ambiente de un proyecto +./core/devtools/environment/scripts/validate-environment.sh /path/to/project + +# Setup de ambiente +./core/devtools/environment/scripts/setup-project-env.sh /path/to/project ``` -## Módulos Disponibles +## Relacion con otras carpetas -### Utils (`@core/modules/utils`) +``` +workspace-v1/ +├── core/ # ARQUITECTURA (este directorio) +│ └── orchestration/ # Sistema SIMCO/NEXUS +│ +├── shared/ # RECURSOS COMPARTIDOS +│ ├── catalog/ # Funcionalidades reutilizables +│ ├── modules/ # Codigo ejecutable +│ ├── constants/ # Constantes globales +│ ├── types/ # Tipos TypeScript +│ └── knowledge-base/ # Documentacion +│ +├── control-plane/ # GOVERNANCE +│ ├── registries/ # Puertos, dominios, BDs +│ ├── manifests/ # Configuraciones de repos +│ └── devtools/ # CI/CD, Docker +│ +├── orchestration/ # DIRECTIVAS A NIVEL WORKSPACE (hereda de core/) +│ +└── projects/ # PROYECTOS DE PRODUCTO +``` -| Archivo | Funciones | Descripción | -|---------|-----------|-------------| -| `date.util.ts` | formatDate, addDays, diffInDays, etc. | Manipulación de fechas | -| `string.util.ts` | slugify, capitalize, truncate, etc. | Manipulación de strings | -| `validation.util.ts` | isEmail, isUUID, isStrongPassword, etc. | Validaciones | +## Ver Tambien -### Constants (`@core/constants`) +- [Sistema de Orquestacion](orchestration/README.md) +- [MCP Servers](mcp-servers/README.md) +- [Recursos Compartidos](../shared/README.md) +- [Catalogo de Funcionalidades](../shared/catalog/README.md) -| Archivo | Contenido | -|---------|-----------| -| `enums.constants.ts` | UserStatus, PaymentStatus, NotificationType, etc. | -| `regex.constants.ts` | EMAIL_REGEX, UUID_REGEX, PHONE_REGEX, etc. | +--- -### Types (`@core/types`) - -| Archivo | Tipos | -|---------|-------| -| `api.types.ts` | ApiResponse, PaginatedResponse, ErrorCodes | -| `common.types.ts` | BaseEntity, Address, Money, Result | - -## Proyectos que Usan Core - -- **Gamilit** - Plataforma educativa de gamificación -- **Trading Platform** - OrbiQuant IA trading -- **ERP Suite** - Sistema ERP multi-vertical - -## Sistema de Orquestación -Los agentes cargan automáticamente las directivas de `core/orchestration/directivas/` al inicializar. - -## Ver También - -- [Sistema de Orquestación](orchestration/README.md) -- [Catálogo de Funcionalidades](catalog/README.md) -- [Plan de Organización](../PLAN-ORGANIZACION-WORKSPACE.md) +**Mantenido por:** Tech-Leader +**Ultima actualizacion:** 2026-01-04 diff --git a/core/mcp-servers/README.md b/core/mcp-servers/README.md new file mode 100644 index 000000000..19e84cb2c --- /dev/null +++ b/core/mcp-servers/README.md @@ -0,0 +1,228 @@ +# MCP Servers + +**Version:** 1.0.0 +**Fecha:** 2026-01-04 +**Sistema:** NEXUS v3.4 + SIMCO + +--- + +## Proposito + +Esta carpeta contiene la infraestructura para MCP (Model Context Protocol) servers del workspace. Los MCP servers proporcionan herramientas especializadas para agentes de IA. + +--- + +## Estructura + +``` +mcp-servers/ +├── README.md # Este archivo +├── _registry.yml # Registro central de MCP servers +│ +├── internal/ # MCP servers desarrollados internamente +│ ├── .gitkeep +│ ├── rag-knowledge/ # [REPO INDEPENDIENTE] Sistema RAG +│ └── scrum-taiga/ # [REPO INDEPENDIENTE] Integracion Taiga +│ +├── external/ # MCP servers de terceros +│ ├── .gitkeep +│ └── _sources.yml # Fuentes confiables +│ +└── templates/ # Templates para nuevos MCP + └── TEMPLATE-MCP-INTERNO/ +``` + +--- + +## Arquitectura: Repositorios Independientes + +Los MCP servers internos son **repositorios independientes** (NO submodules): + +| Caracteristica | Valor | +|----------------|-------| +| Versionado | Independiente del workspace | +| Dependencias | Propias (node_modules excluidos) | +| Desarrollo | Ciclo de vida propio | +| Clonacion | Manual despues de workspace | + +### Por que NO son submodules? + +1. **Flexibilidad:** Pueden actualizarse sin afectar workspace +2. **Desarrollo independiente:** Equipos pueden trabajar en paralelo +3. **Reutilizacion:** Pueden usarse en otros contextos +4. **Simplicidad:** Sin complejidad de submodules anidados + +--- + +## MCP Servers Disponibles + +### Internos (Desarrollo Propio) + +| MCP Server | Descripcion | Prioridad | Estado | +|------------|-------------|-----------|--------| +| **rag-knowledge** | Sistema RAG como fuente de verdad | MAXIMA | Planificado | +| **scrum-taiga** | Integracion con Taiga SCRUM | ALTA | Planificado | + +### Externos (Terceros) + +Ver `external/_sources.yml` para fuentes confiables de MCP servers externos. + +--- + +## Instalacion + +### 1. Requisitos Previos + +- workspace-v1 clonado +- SSH configurado para Gitea (ver `orchestration/referencias/REPOSITORY-STRUCTURE.md`) +- Node.js >= 18 + +### 2. Clonar MCP Servers + +```bash +# Navegar a carpeta de MCP internos +cd /home/isem/workspace-v1/core/mcp-servers/internal + +# Clonar RAG Knowledge Base (recomendado para desarrollo) +git clone git@gitea-server:rckrdmrd/mcp-rag-knowledge.git rag-knowledge + +# Clonar SCRUM Taiga (opcional) +git clone git@gitea-server:rckrdmrd/mcp-scrum-taiga.git scrum-taiga +``` + +### 3. Instalar Dependencias + +```bash +# RAG Knowledge +cd rag-knowledge +npm install +cp .env.example .env +# Configurar .env con credenciales + +# SCRUM Taiga +cd ../scrum-taiga +npm install +cp .env.example .env +# Configurar .env con credenciales Taiga +``` + +### 4. Verificar Instalacion + +```bash +# Desde workspace-v1 +cd /home/isem/workspace-v1 + +# Verificar estructura +ls -la core/mcp-servers/internal/ + +# Verificar que MCP servers responden +cd core/mcp-servers/internal/rag-knowledge +npm run health-check +``` + +--- + +## Desarrollo de Nuevos MCP + +### Usar Template + +```bash +# Copiar template +cp -r templates/TEMPLATE-MCP-INTERNO nuevo-mcp-server + +# Personalizar +cd nuevo-mcp-server +# Editar package.json, README.md, etc. +``` + +### Directivas a Seguir + +| Directiva | Proposito | +|-----------|-----------| +| @SIMCO_MCP | Desarrollo de MCP servers | +| @SIMCO_MCP_IMPORT | Importacion de MCP externos | +| @SIMCO_RAG | Interaccion con sistema RAG | + +### Registrar en _registry.yml + +Despues de crear un nuevo MCP server, agregarlo a `_registry.yml`: + +```yaml +mcp_servers: + internal: + nuevo-mcp: + name: "Nombre del MCP" + description: "Descripcion" + status: "development" + repository: + url: "git@gitea-server:rckrdmrd/mcp-nuevo.git" + # ... +``` + +--- + +## Perfiles de Agentes Relacionados + +| Perfil | Responsabilidad | +|--------|-----------------| +| @PERFIL_MCP_ARCHITECT | Diseno e integracion de MCP | +| @PERFIL_MCP_DEVELOPER | Desarrollo de MCP internos | +| @PERFIL_MCP_INTEGRATOR | Importacion de MCP externos | +| @PERFIL_RAG_ENGINEER | Mantenimiento del RAG | + +--- + +## Aliases Utiles + +```yaml +@MCP_SERVERS: "core/mcp-servers/" +@MCP_REGISTRY: "core/mcp-servers/_registry.yml" +@MCP_RAG: "core/mcp-servers/internal/rag-knowledge/" +@MCP_TAIGA: "core/mcp-servers/internal/scrum-taiga/" +``` + +--- + +## Troubleshooting + +### MCP server no clonado + +```bash +# Verificar SSH +ssh -T gitea-server + +# Si falla, verificar ~/.ssh/config +cat ~/.ssh/config | grep gitea +``` + +### Dependencias faltantes + +```bash +# Reinstalar +cd core/mcp-servers/internal/rag-knowledge +rm -rf node_modules +npm install +``` + +### Variables de entorno + +```bash +# Verificar .env existe +ls -la .env + +# Verificar variables requeridas +cat .env.example +``` + +--- + +## Referencias + +- `_registry.yml` - Registro completo de MCP servers +- `orchestration/directivas/simco/SIMCO-MCP.md` - Directiva de desarrollo +- `orchestration/referencias/REPOSITORY-STRUCTURE.md` - Estructura de repos + +--- + +**Mantenido por:** @PERFIL_MCP_ARCHITECT +**Sistema:** NEXUS v3.4 + SIMCO diff --git a/core/mcp-servers/_registry.yml b/core/mcp-servers/_registry.yml new file mode 100644 index 000000000..a05287de6 --- /dev/null +++ b/core/mcp-servers/_registry.yml @@ -0,0 +1,172 @@ +# MCP Servers Registry +# ==================== +# Registro central de todos los MCP servers del workspace +# +# Version: 1.0.0 +# Fecha: 2026-01-04 +# Sistema: NEXUS v3.4 + SIMCO + +version: "1.0.0" +last_updated: "2026-01-04" +maintainer: "@PERFIL_MCP_ARCHITECT" + +# ============================================================================ +# MCP SERVERS INTERNOS +# ============================================================================ +# Desarrollados internamente para necesidades especificas del workspace +# Cada uno es un repositorio independiente que se clona manualmente + +mcp_servers: + internal: + # ------------------------------------------------------------------------- + # RAG Knowledge Base - PRIORIDAD MAXIMA + # ------------------------------------------------------------------------- + rag-knowledge: + name: "RAG Knowledge Base" + description: | + Sistema RAG (Retrieval-Augmented Generation) como fuente de verdad + del workspace. Proporciona busqueda semantica sobre documentacion, + directivas, perfiles y codigo. + status: "planned" + priority: "maxima" + + repository: + type: "gitea" + url: "git@gitea-server:rckrdmrd/mcp-rag-knowledge.git" + https: "http://72.60.226.4:3000/rckrdmrd/mcp-rag-knowledge" + + clone_path: "core/mcp-servers/internal/rag-knowledge" + + dependencies: + runtime: + - "Node.js >= 18" + - "TypeScript >= 5.0" + database: + - "PostgreSQL >= 15" + - "pgvector extension" + external_apis: + - "OpenAI API (embeddings)" + + tools_provided: + - rag_query_context + - rag_get_directive + - rag_get_agent_profile + - rag_trace_reference + - rag_get_relations + - rag_find_code + - rag_explain_impact + - rag_index_document + - rag_sync_category + - rag_get_sync_status + - rag_validate_coverage + - rag_report_feedback + + documentation: + architecture: "docs/ARCHITECTURE.md" + deployment: "docs/DEPLOYMENT.md" + usage: "docs/USAGE.md" + tools_spec: "docs/MCP-TOOLS-SPEC.md" + + # ------------------------------------------------------------------------- + # SCRUM Taiga Integration - PRIORIDAD ALTA + # ------------------------------------------------------------------------- + scrum-taiga: + name: "SCRUM Taiga Integration" + description: | + Integracion con Taiga para gestion de proyectos SCRUM. + Permite sincronizar EPICs, User Stories y Tasks entre + el workspace y Taiga. + status: "planned" + priority: "alta" + + repository: + type: "gitea" + url: "git@gitea-server:rckrdmrd/mcp-scrum-taiga.git" + https: "http://72.60.226.4:3000/rckrdmrd/mcp-scrum-taiga" + + clone_path: "core/mcp-servers/internal/scrum-taiga" + + dependencies: + runtime: + - "Node.js >= 18" + - "TypeScript >= 5.0" + external_apis: + - "Taiga API" + + tools_provided: + - taiga_get_project + - taiga_list_epics + - taiga_create_epic + - taiga_list_user_stories + - taiga_create_user_story + - taiga_list_tasks + - taiga_create_task + - taiga_update_status + - taiga_sync_sprint + + documentation: + architecture: "docs/ARCHITECTURE.md" + deployment: "docs/DEPLOYMENT.md" + usage: "docs/USAGE.md" + +# ============================================================================ +# MCP SERVERS EXTERNOS +# ============================================================================ +# MCP servers de terceros evaluados y aprobados para uso + + external: + # Lista de MCP servers externos pendientes de evaluacion + pending_evaluation: [] + + # MCP servers externos aprobados + approved: [] + + # Fuentes confiables para buscar MCP servers + trusted_sources: + - name: "Anthropic Official" + url: "https://github.com/anthropics" + priority: 1 + - name: "MCP Community" + url: "https://github.com/modelcontextprotocol" + priority: 2 + +# ============================================================================ +# INSTRUCCIONES DE CLONACION +# ============================================================================ + +clone_instructions: | + # Despues de clonar workspace-v1, clonar los MCP servers necesarios: + + # 1. Navegar a la carpeta de MCP internos + cd /home/isem/workspace-v1/core/mcp-servers/internal + + # 2. Clonar RAG Knowledge Base (recomendado) + git clone git@gitea-server:rckrdmrd/mcp-rag-knowledge.git rag-knowledge + cd rag-knowledge && npm install && cd .. + + # 3. Clonar SCRUM Taiga (opcional) + git clone git@gitea-server:rckrdmrd/mcp-scrum-taiga.git scrum-taiga + cd scrum-taiga && npm install && cd .. + + # 4. Verificar instalacion + ls -la + +# ============================================================================ +# VALIDACION +# ============================================================================ + +validation: + required_for_development: + - rag-knowledge + optional: + - scrum-taiga + + check_command: | + # Verificar que MCP servers estan clonados + for mcp in rag-knowledge scrum-taiga; do + if [ -d "core/mcp-servers/internal/$mcp" ]; then + echo "OK: $mcp" + else + echo "MISSING: $mcp (clone con: git clone git@gitea-server:rckrdmrd/mcp-$mcp.git)" + fi + done diff --git a/core/mcp-servers/external/.gitkeep b/core/mcp-servers/external/.gitkeep new file mode 100644 index 000000000..5e098d414 --- /dev/null +++ b/core/mcp-servers/external/.gitkeep @@ -0,0 +1,2 @@ +# Esta carpeta contiene MCP servers externos evaluados e instalados +# Ver _sources.yml para fuentes confiables diff --git a/core/mcp-servers/external/_sources.yml b/core/mcp-servers/external/_sources.yml new file mode 100644 index 000000000..894916b46 --- /dev/null +++ b/core/mcp-servers/external/_sources.yml @@ -0,0 +1,125 @@ +# MCP External Sources +# ==================== +# Fuentes confiables para MCP servers externos +# +# Version: 1.0.0 +# Fecha: 2026-01-04 + +version: "1.0.0" +last_updated: "2026-01-04" + +# ============================================================================ +# FUENTES CONFIABLES +# ============================================================================ +# Lista de repositorios/organizaciones confiables para MCP servers + +trusted_sources: + # Nivel 1: Oficial (maxima confianza) + official: + - name: "Anthropic Official" + url: "https://github.com/anthropics" + description: "MCP servers oficiales de Anthropic" + trust_level: "official" + auto_approve: true + + - name: "Model Context Protocol" + url: "https://github.com/modelcontextprotocol" + description: "Repositorio oficial del protocolo MCP" + trust_level: "official" + auto_approve: true + + # Nivel 2: Comunidad Verificada (alta confianza) + community_verified: + - name: "MCP Community Servers" + url: "https://github.com/mcp-community" + description: "Servidores MCP de la comunidad verificados" + trust_level: "verified" + auto_approve: false + requires_review: true + + # Nivel 3: Terceros (requiere evaluacion completa) + third_party: [] + +# ============================================================================ +# PROCESO DE EVALUACION +# ============================================================================ + +evaluation_process: + steps: + 1_identify: + description: "Identificar MCP server de interes" + actions: + - Verificar fuente en trusted_sources + - Revisar repositorio y documentacion + - Verificar actividad y mantenimiento + + 2_security_review: + description: "Revision de seguridad" + actions: + - Revisar dependencias (npm audit) + - Buscar vulnerabilidades conocidas + - Verificar permisos requeridos + - Revisar codigo fuente si es necesario + + 3_functionality_test: + description: "Prueba de funcionalidad" + actions: + - Instalar en ambiente de prueba + - Ejecutar tests incluidos + - Verificar herramientas funcionan + - Documentar comportamiento + + 4_approval: + description: "Aprobacion final" + actions: + - Documentar en _registry.yml + - Agregar a external/installed/ + - Actualizar documentacion + + criteria: + mandatory: + - "Codigo fuente disponible" + - "Sin vulnerabilidades criticas" + - "Documentacion adecuada" + - "Tests incluidos" + recommended: + - "Mantenimiento activo (< 6 meses)" + - "Mas de 10 estrellas en GitHub" + - "Issues respondidos" + +# ============================================================================ +# MCP SERVERS EXTERNOS INSTALADOS +# ============================================================================ + +installed: [] + # Formato cuando se instale uno: + # - name: "nombre-del-mcp" + # source: "url del repo" + # version: "1.0.0" + # installed_date: "2026-01-04" + # installed_by: "@PERFIL_MCP_INTEGRATOR" + # location: "external/installed/nombre-del-mcp" + # review_notes: "Notas de la evaluacion" + +# ============================================================================ +# MCP SERVERS PENDIENTES DE EVALUACION +# ============================================================================ + +pending_evaluation: [] + # Formato: + # - name: "nombre" + # source: "url" + # requested_by: "quien lo solicito" + # requested_date: "fecha" + # reason: "por que se necesita" + +# ============================================================================ +# MCP SERVERS RECHAZADOS +# ============================================================================ + +rejected: [] + # Formato: + # - name: "nombre" + # source: "url" + # rejected_date: "fecha" + # reason: "razon del rechazo" diff --git a/core/mcp-servers/internal/.gitkeep b/core/mcp-servers/internal/.gitkeep new file mode 100644 index 000000000..8ba7fa914 --- /dev/null +++ b/core/mcp-servers/internal/.gitkeep @@ -0,0 +1,3 @@ +# Esta carpeta contiene MCP servers internos como repositorios independientes +# Cada MCP server se clona manualmente despues de clonar workspace-v1 +# Ver _registry.yml para lista de MCP servers disponibles diff --git a/core/mcp-servers/templates/.gitkeep b/core/mcp-servers/templates/.gitkeep new file mode 100644 index 000000000..075646e93 --- /dev/null +++ b/core/mcp-servers/templates/.gitkeep @@ -0,0 +1 @@ +# Esta carpeta contiene templates para crear nuevos MCP servers diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/.env.example b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/.env.example new file mode 100644 index 000000000..eebf74e0b --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/.env.example @@ -0,0 +1,16 @@ +# ============================================================================ +# MCP Server Configuration +# ============================================================================ + +# Server +PORT=3100 +NODE_ENV=development + +# Database (PostgreSQL with pgvector) +DATABASE_URL=postgresql://user:password@localhost:5432/mcp_db + +# OpenAI (for embeddings) +OPENAI_API_KEY=sk-your-api-key-here + +# Logging +LOG_LEVEL=info diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/.gitignore b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/.gitignore new file mode 100644 index 000000000..6fc8575b9 --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/.gitignore @@ -0,0 +1,35 @@ +# Dependencies +node_modules/ + +# Build +dist/ +*.tsbuildinfo + +# Environment +.env +.env.local +.env.*.local + +# Logs +logs/ +*.log +npm-debug.log* + +# Testing +coverage/ +.nyc_output/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Temp +tmp/ +temp/ +.cache/ diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/README.md b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/README.md new file mode 100644 index 000000000..41340c837 --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/README.md @@ -0,0 +1,121 @@ +# {NOMBRE_MCP} + +**Version:** 0.1.0 +**Fecha:** {FECHA} +**Sistema:** NEXUS v3.4 + SIMCO + +--- + +## Descripcion + +{DESCRIPCION_DEL_MCP} + +--- + +## Instalacion + +```bash +# Clonar (desde workspace-v1/core/mcp-servers/internal/) +git clone git@gitea-server:rckrdmrd/mcp-{nombre}.git {nombre} +cd {nombre} + +# Instalar dependencias +npm install + +# Configurar +cp .env.example .env +# Editar .env con credenciales +``` + +--- + +## Configuracion + +### Variables de Entorno + +| Variable | Descripcion | Requerido | +|----------|-------------|-----------| +| `DATABASE_URL` | URL de PostgreSQL | Si | +| `OPENAI_API_KEY` | API key de OpenAI | Si | + +### Archivo .env.example + +```env +# Database +DATABASE_URL=postgresql://user:pass@localhost:5432/db + +# OpenAI (para embeddings) +OPENAI_API_KEY=sk-... + +# Server +PORT=3100 +``` + +--- + +## Uso + +### Iniciar Servidor + +```bash +npm run start +``` + +### Desarrollo + +```bash +npm run dev +``` + +### Tests + +```bash +npm run test +``` + +--- + +## Herramientas MCP Disponibles + +| Herramienta | Descripcion | +|-------------|-------------| +| `{tool_1}` | {descripcion} | +| `{tool_2}` | {descripcion} | + +--- + +## Estructura + +``` +{nombre}/ +├── README.md +├── package.json +├── tsconfig.json +├── .env.example +├── .gitignore +├── docs/ +│ ├── ARCHITECTURE.md +│ ├── DEPLOYMENT.md +│ └── USAGE.md +├── orchestration/ +│ └── 00-guidelines/ +│ └── CONTEXTO-PROYECTO.md +├── src/ +│ ├── index.ts +│ ├── tools/ +│ └── services/ +├── config/ +└── tests/ +``` + +--- + +## Referencias + +- Directiva: @SIMCO_MCP +- Perfil: @PERFIL_MCP_DEVELOPER +- Registry: core/mcp-servers/_registry.yml + +--- + +**Mantenido por:** @PERFIL_MCP_DEVELOPER diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/config/.gitkeep b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/config/.gitkeep new file mode 100644 index 000000000..24e10875d --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/config/.gitkeep @@ -0,0 +1 @@ +# Configuration files directory diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/config/chunking-strategies.yml b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/config/chunking-strategies.yml new file mode 100644 index 000000000..2062665ef --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/config/chunking-strategies.yml @@ -0,0 +1,318 @@ +# ============================================================================ +# CHUNKING-STRATEGIES.yml +# Estrategias de Chunking para Sistema RAG +# ============================================================================ +# Version: 1.0.0 +# Fecha: 2026-01-04 +# EPIC: EPIC-013 +# ============================================================================ + +# ---------------------------------------------------------------------------- +# CONFIGURACION GLOBAL +# ---------------------------------------------------------------------------- +global: + # Modelo de embeddings a usar + embedding_model: "text-embedding-ada-002" + embedding_dimensions: 1536 + + # Limites generales + max_chunk_size: 1500 # Caracteres maximos por chunk + min_chunk_size: 100 # Caracteres minimos (evitar chunks muy pequeños) + chunk_overlap: 200 # Overlap entre chunks consecutivos + + # Separadores de chunk (en orden de prioridad) + separators: + - "\n## " # Heading nivel 2 + - "\n### " # Heading nivel 3 + - "\n#### " # Heading nivel 4 + - "\n\n" # Parrafo + - "\n" # Linea + - ". " # Oracion + - " " # Palabra (ultimo recurso) + +# ---------------------------------------------------------------------------- +# ESTRATEGIAS POR TIPO DE DOCUMENTO +# ---------------------------------------------------------------------------- +strategies: + + # -------------------------------------------------------------------------- + # DIRECTIVAS SIMCO + # -------------------------------------------------------------------------- + directiva: + description: "Documentos de directivas del sistema SIMCO" + file_patterns: + - "orchestration/directivas/**/*.md" + - "**/SIMCO-*.md" + + chunking: + method: "semantic" # semantic | fixed | paragraph + preserve_headings: true # Mantener jerarquia de headings en cada chunk + max_chunk_size: 1200 # Directivas son densos, chunks mas pequenos + include_frontmatter: true # Incluir frontmatter en primer chunk + + metadata_extraction: + - field: "version" + pattern: "Version:\\s*([\\d.]+)" + - field: "priority" + pattern: "Prioridad:\\s*(\\w+)" + - field: "applies_to" + pattern: "Aplica a:\\s*(.+)" + + heading_weights: + "RESUMEN EJECUTIVO": 1.5 # Boost para secciones importantes + "PRINCIPIO FUNDAMENTAL": 1.5 + "CHECKLIST": 1.3 + + # -------------------------------------------------------------------------- + # PERFILES DE AGENTES + # -------------------------------------------------------------------------- + perfil: + description: "Perfiles de agentes del sistema NEXUS" + file_patterns: + - "orchestration/perfiles/**/*.md" + - "**/PERFIL-*.md" + + chunking: + method: "semantic" + preserve_headings: true + max_chunk_size: 1000 # Perfiles necesitan precision alta + include_frontmatter: true + + metadata_extraction: + - field: "agent_id" + pattern: "@(PERFIL_[A-Z_]+)" + - field: "system" + pattern: "Sistema:\\s*(\\w+)" + - field: "context_level" + pattern: "Contexto:\\s*(\\w+)" + + special_sections: + - name: "DIRECTIVAS APLICABLES" + extract_as: "applicable_directives" + is_list: true + - name: "HERRAMIENTAS MCP" + extract_as: "mcp_tools" + is_list: true + + # -------------------------------------------------------------------------- + # TEMPLATES + # -------------------------------------------------------------------------- + template: + description: "Templates y plantillas del workspace" + file_patterns: + - "**/templates/**/*" + - "**/TEMPLATE-*.md" + + chunking: + method: "fixed" # Templates se dividen por tamano fijo + preserve_code_blocks: true # No cortar bloques de codigo + max_chunk_size: 2000 # Templates pueden ser mas largos + + metadata_extraction: + - field: "template_type" + pattern: "Tipo:\\s*(\\w+)" + - field: "usage" + pattern: "Uso:\\s*(.+)" + + special_handling: + - pattern: "```" + action: "preserve_block" # Mantener bloques de codigo intactos + + # -------------------------------------------------------------------------- + # CODIGO FUENTE + # -------------------------------------------------------------------------- + code: + description: "Archivos de codigo fuente" + file_patterns: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" + - "**/*.sql" + - "**/*.py" + + chunking: + method: "ast" # Usar Abstract Syntax Tree + preserve_functions: true # No cortar funciones a la mitad + include_context: true # Incluir imports y contexto + max_chunk_size: 2500 # Codigo puede necesitar mas contexto + + metadata_extraction: + - field: "language" + from: "file_extension" + - field: "exports" + pattern: "export\\s+(function|class|const|type)\\s+(\\w+)" + - field: "imports" + pattern: "import\\s+.*from\\s+['\"](.+)['\"]" + + special_sections: + - type: "function" + extract_signature: true + extract_jsdoc: true + - type: "class" + extract_signature: true + include_methods: true + + # -------------------------------------------------------------------------- + # ESPECIFICACIONES + # -------------------------------------------------------------------------- + spec: + description: "Documentos de especificacion tecnica" + file_patterns: + - "**/specs/**/*.md" + - "**/SPEC-*.md" + - "**/DDL-*.sql" + + chunking: + method: "semantic" + preserve_headings: true + preserve_code_blocks: true + max_chunk_size: 1800 + + metadata_extraction: + - field: "spec_type" + pattern: "Tipo:\\s*(\\w+)" + - field: "version" + pattern: "Version:\\s*([\\d.]+)" + + special_handling: + - pattern: "CREATE TABLE" + action: "preserve_statement" + - pattern: "CREATE FUNCTION" + action: "preserve_statement" + + # -------------------------------------------------------------------------- + # GUIAS Y DOCUMENTACION + # -------------------------------------------------------------------------- + guide: + description: "Guias de uso y documentacion general" + file_patterns: + - "**/docs/**/*.md" + - "**/README.md" + - "**/USAGE.md" + - "**/GUIDE-*.md" + + chunking: + method: "paragraph" # Por parrafos naturales + preserve_headings: true + preserve_code_blocks: true + max_chunk_size: 1500 + + metadata_extraction: + - field: "doc_type" + from: "filename" + - field: "project" + from: "path_segment" + segment_index: 1 + + # -------------------------------------------------------------------------- + # EPICS Y TAREAS + # -------------------------------------------------------------------------- + epic: + description: "Documentos de EPICs y planificacion" + file_patterns: + - "**/EPIC-*.md" + - "**/epics/**/*.md" + + chunking: + method: "semantic" + preserve_headings: true + max_chunk_size: 1200 + + metadata_extraction: + - field: "epic_id" + pattern: "EPIC-(\\d+)" + - field: "status" + pattern: "Estado:\\s*(\\w+)" + - field: "priority" + pattern: "Prioridad:\\s*(\\w+)" + + special_sections: + - name: "TAREAS" + extract_as: "tasks" + is_list: true + - name: "DEPENDENCIAS" + extract_as: "dependencies" + is_list: true + + # -------------------------------------------------------------------------- + # TRAZAS DE SESION + # -------------------------------------------------------------------------- + traza: + description: "Trazas de sesiones de agentes" + file_patterns: + - "**/trazas/TRAZA-*.md" + + chunking: + method: "fixed" + max_chunk_size: 2000 # Trazas son largas + preserve_code_blocks: true + + metadata_extraction: + - field: "session_id" + pattern: "TRAZA-(\\d+)" + - field: "agent" + pattern: "Agente:\\s*@(\\w+)" + - field: "date" + pattern: "Fecha:\\s*([\\d-]+)" + + indexing: + priority: "low" # Trazas tienen menor prioridad + retention_days: 90 # Mantener por 90 dias + +# ---------------------------------------------------------------------------- +# PROCESAMIENTO ESPECIAL +# ---------------------------------------------------------------------------- +preprocessing: + # Limpiar antes de chunking + cleanup: + - remove_html_comments: true + - normalize_whitespace: true + - convert_tabs_to_spaces: true + + # Frontmatter YAML + frontmatter: + extract: true + include_in_first_chunk: true + fields_to_index: + - "title" + - "version" + - "applicable_agents" + - "priority" + +postprocessing: + # Agregar contexto a cada chunk + add_context: + - document_title: true + - heading_path: true + - document_category: true + + # Validacion + validation: + - min_length: 50 + - max_length: 3000 + - require_content: true + +# ---------------------------------------------------------------------------- +# CONFIGURACION DE EMBEDDINGS +# ---------------------------------------------------------------------------- +embeddings: + # Proveedor + provider: "openai" + model: "text-embedding-ada-002" + dimensions: 1536 + + # Batching + batch_size: 100 + max_retries: 3 + retry_delay_ms: 1000 + + # Cache + cache: + enabled: true + ttl_hours: 168 # 7 dias + storage: "postgres" + +# ---------------------------------------------------------------------------- +# FIN DE CONFIGURACION +# ---------------------------------------------------------------------------- diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/config/path-mappings.yml b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/config/path-mappings.yml new file mode 100644 index 000000000..db7de1d66 --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/config/path-mappings.yml @@ -0,0 +1,359 @@ +# ============================================================================ +# PATH-MAPPINGS.yml +# Mapeo de Rutas del Workspace a Categorias RAG +# ============================================================================ +# Version: 1.0.0 +# Fecha: 2026-01-04 +# EPIC: EPIC-013 +# ============================================================================ + +# ---------------------------------------------------------------------------- +# CONFIGURACION BASE +# ---------------------------------------------------------------------------- +base: + workspace_root: "/home/isem/workspace-v1" + + # Categorias principales del sistema RAG + categories: + - orchestration # Sistema de orquestacion (directivas, perfiles, trazas) + - core # Componentes core (MCP servers, utilidades) + - knowledge-base # Base de conocimiento (documentacion, snippets) + - projects # Proyectos activos (gamilit, erp-core, etc) + +# ---------------------------------------------------------------------------- +# MAPEOS DE RUTAS +# ---------------------------------------------------------------------------- +mappings: + + # ========================================================================== + # ORCHESTRATION - Sistema de Orquestacion + # ========================================================================== + orchestration: + base_path: "orchestration/" + description: "Sistema NEXUS de orquestacion de agentes" + priority: "maxima" + + subcategories: + # Directivas SIMCO + directivas: + paths: + - "orchestration/directivas/**/*.md" + document_type: "directiva" + applicable_agents: ["*"] + index_priority: 1 + + # Perfiles de agentes + perfiles: + paths: + - "orchestration/perfiles/**/*.md" + document_type: "perfil" + applicable_agents: ["*"] + index_priority: 1 + + # Templates de orquestacion + templates: + paths: + - "orchestration/templates/**/*" + document_type: "template" + applicable_agents: ["PERFIL_ARCHITECT", "PERFIL_DEVELOPER"] + index_priority: 2 + + # Trazas de sesiones + trazas: + paths: + - "orchestration/trazas/**/*.md" + document_type: "traza" + applicable_agents: ["PERFIL_ANALYST"] + index_priority: 3 + retention_days: 90 + + # Referencias + referencias: + paths: + - "orchestration/referencias/**/*.md" + document_type: "reference" + applicable_agents: ["*"] + index_priority: 2 + + # EPICs + epics: + paths: + - "orchestration/epics/**/*.md" + document_type: "epic" + applicable_agents: ["PERFIL_SCRUM_MANAGER", "PERFIL_ARCHITECT"] + index_priority: 2 + + # ========================================================================== + # CORE - Componentes Core del Workspace + # ========================================================================== + core: + base_path: "core/" + description: "Componentes core compartidos" + priority: "alta" + + subcategories: + # MCP Servers + mcp-servers: + paths: + - "core/mcp-servers/**/*.md" + - "core/mcp-servers/**/*.yml" + - "core/mcp-servers/**/*.yaml" + - "core/mcp-servers/templates/**/*" + document_type: "spec" + applicable_agents: ["PERFIL_MCP_ARCHITECT", "PERFIL_MCP_DEVELOPER"] + index_priority: 1 + exclude: + - "core/mcp-servers/internal/*/node_modules/**" + - "core/mcp-servers/internal/*/.git/**" + - "core/mcp-servers/external/installed/**" + + # Utilidades compartidas + utils: + paths: + - "core/utils/**/*" + document_type: "code" + applicable_agents: ["PERFIL_DEVELOPER"] + index_priority: 2 + + # Scripts + scripts: + paths: + - "core/scripts/**/*" + document_type: "code" + applicable_agents: ["PERFIL_DEVOPS"] + index_priority: 3 + + # ========================================================================== + # KNOWLEDGE-BASE - Base de Conocimiento + # ========================================================================== + knowledge-base: + base_path: "knowledge-base/" + description: "Documentacion y recursos de referencia" + priority: "alta" + + subcategories: + # Documentacion tecnica + technical: + paths: + - "knowledge-base/technical/**/*.md" + document_type: "guide" + applicable_agents: ["*"] + index_priority: 2 + + # Snippets de codigo + snippets: + paths: + - "knowledge-base/snippets/**/*" + document_type: "code" + applicable_agents: ["PERFIL_DEVELOPER"] + index_priority: 2 + + # Mejores practicas + best-practices: + paths: + - "knowledge-base/best-practices/**/*.md" + document_type: "guide" + applicable_agents: ["*"] + index_priority: 2 + + # Patrones de diseno + patterns: + paths: + - "knowledge-base/patterns/**/*.md" + document_type: "guide" + applicable_agents: ["PERFIL_ARCHITECT", "PERFIL_DEVELOPER"] + index_priority: 2 + + # ========================================================================== + # PROJECTS - Proyectos Activos + # ========================================================================== + projects: + base_path: "projects/" + description: "Proyectos en desarrollo activo" + priority: "alta" + + # Proyectos especificos + project_mappings: + + # Gamilit - Sistema educativo + gamilit: + paths: + - "projects/gamilit/orchestration/**/*.md" + - "projects/gamilit/docs/**/*.md" + - "projects/gamilit/apps/*/README.md" + document_type: "spec" + project: "gamilit" + applicable_agents: + - "PERFIL_ARCHITECT" + - "PERFIL_BACKEND_DEVELOPER" + - "PERFIL_FRONTEND_DEVELOPER" + index_priority: 1 + exclude: + - "projects/gamilit/**/node_modules/**" + - "projects/gamilit/**/dist/**" + - "projects/gamilit/**/.git/**" + + # ERP Core - Sistema ERP + erp-core: + paths: + - "projects/erp-core/orchestration/**/*.md" + - "projects/erp-core/docs/**/*.md" + document_type: "spec" + project: "erp-core" + applicable_agents: + - "PERFIL_ARCHITECT" + - "PERFIL_DEVELOPER" + index_priority: 2 + + # Template de proyecto (para nuevos proyectos) + _template: + paths: + - "projects/*/orchestration/**/*.md" + - "projects/*/docs/**/*.md" + document_type: "spec" + index_priority: 3 + +# ---------------------------------------------------------------------------- +# EXCLUSIONES GLOBALES +# ---------------------------------------------------------------------------- +global_exclusions: + # Carpetas de dependencias + - "**/node_modules/**" + - "**/.npm/**" + - "**/vendor/**" + + # Carpetas de build + - "**/dist/**" + - "**/build/**" + - "**/.next/**" + - "**/coverage/**" + + # Control de versiones + - "**/.git/**" + - "**/.svn/**" + + # Cache y temporales + - "**/.cache/**" + - "**/tmp/**" + - "**/.tmp/**" + - "**/temp/**" + + # Logs + - "**/logs/**" + - "**/*.log" + + # IDE y editores + - "**/.idea/**" + - "**/.vscode/**" + - "**/*.swp" + - "**/*.swo" + + # Archivos binarios + - "**/*.png" + - "**/*.jpg" + - "**/*.jpeg" + - "**/*.gif" + - "**/*.ico" + - "**/*.pdf" + - "**/*.zip" + - "**/*.tar.gz" + +# ---------------------------------------------------------------------------- +# REGLAS DE DETECCION DE TIPO +# ---------------------------------------------------------------------------- +type_detection: + # Por nombre de archivo + by_filename: + - pattern: "SIMCO-*.md" + type: "directiva" + - pattern: "PERFIL-*.md" + type: "perfil" + - pattern: "TEMPLATE-*.md" + type: "template" + - pattern: "EPIC-*.md" + type: "epic" + - pattern: "TRAZA-*.md" + type: "traza" + - pattern: "DDL-*.sql" + type: "spec" + - pattern: "README.md" + type: "guide" + - pattern: "*.test.ts" + type: "test" + + # Por extension + by_extension: + - extension: ".md" + default_type: "guide" + - extension: ".ts" + type: "code" + - extension: ".tsx" + type: "code" + - extension: ".sql" + type: "spec" + - extension: ".yml" + type: "config" + - extension: ".yaml" + type: "config" + +# ---------------------------------------------------------------------------- +# RELACIONES AUTOMATICAS +# ---------------------------------------------------------------------------- +auto_relations: + # Detectar referencias entre documentos + reference_patterns: + - pattern: "@(SIMCO[/_][A-Z-]+)" + relation_type: "references" + target_category: "orchestration" + + - pattern: "@(PERFIL_[A-Z_]+)" + relation_type: "references" + target_category: "orchestration" + + - pattern: "EPIC-(\\d+)" + relation_type: "references" + target_category: "orchestration" + + - pattern: "MEJ-(\\d+)-(\\d+)" + relation_type: "implements" + target_category: "orchestration" + + # Detectar imports en codigo + code_imports: + - pattern: "from ['\"](.+)['\"]" + relation_type: "imports" + resolve_path: true + +# ---------------------------------------------------------------------------- +# SINCRONIZACION +# ---------------------------------------------------------------------------- +sync: + # Frecuencia de sincronizacion por categoria + schedules: + orchestration: + frequency: "realtime" # Sincronizar inmediatamente al cambiar + + core: + frequency: "hourly" # Cada hora + + knowledge-base: + frequency: "daily" # Diariamente + + projects: + frequency: "on_demand" # Solo cuando se solicite + + # Hooks de sincronizacion + hooks: + pre_sync: + - validate_frontmatter: true + - check_file_size: true + - max_file_size_mb: 5 + + post_sync: + - update_relations: true + - validate_coverage: true + - notify_if_errors: true + +# ---------------------------------------------------------------------------- +# FIN DE CONFIGURACION +# ---------------------------------------------------------------------------- diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/docs/ARCHITECTURE.md b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/docs/ARCHITECTURE.md new file mode 100644 index 000000000..fae8c17ec --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/docs/ARCHITECTURE.md @@ -0,0 +1,103 @@ +# Arquitectura: MCP {NOMBRE} + +**Version:** 0.1.0 +**Fecha:** {FECHA} + +--- + +## 1. Vision General + +``` +┌─────────────────────────────────────────────────────────┐ +│ Claude / Agente │ +└───────────────────────────┬─────────────────────────────┘ + │ MCP Protocol + v +┌─────────────────────────────────────────────────────────┐ +│ MCP Server {NOMBRE} │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Tools │ │ Services │ │ Config │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└───────────────────────────┬─────────────────────────────┘ + │ + v +┌─────────────────────────────────────────────────────────┐ +│ PostgreSQL + pgvector │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. Componentes + +### 2.1 Tools Layer + +Herramientas expuestas via MCP Protocol: + +| Tool | Descripcion | +|------|-------------| +| `{tool_1}` | {desc} | +| `{tool_2}` | {desc} | + +### 2.2 Services Layer + +Logica de negocio: + +| Service | Responsabilidad | +|---------|-----------------| +| `{Service1}` | {resp} | +| `{Service2}` | {resp} | + +### 2.3 Data Layer + +Acceso a datos: + +- PostgreSQL para almacenamiento persistente +- pgvector para busqueda semantica + +--- + +## 3. Flujo de Datos + +``` +Request (Tool Call) + │ + v + Validate Input + │ + v + Service Logic + │ + v + Database Query + │ + v + Format Response + │ + v +Response (Tool Result) +``` + +--- + +## 4. Tecnologias + +| Componente | Tecnologia | +|------------|------------| +| Runtime | Node.js 18+ | +| Lenguaje | TypeScript 5+ | +| Database | PostgreSQL 15+ | +| Vector Search | pgvector | +| HTTP | Express | + +--- + +## 5. Seguridad + +- Variables sensibles en .env (no versionado) +- Validacion de entrada en cada tool +- Conexion a DB via SSL en produccion + +--- + +**Documento generado:** {FECHA} diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/docs/DDL-RAG-SCHEMA.sql b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/docs/DDL-RAG-SCHEMA.sql new file mode 100644 index 000000000..fa9536a56 --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/docs/DDL-RAG-SCHEMA.sql @@ -0,0 +1,432 @@ +-- ============================================================================ +-- DDL-RAG-SCHEMA.sql +-- Schema de Base de Datos para Sistema RAG +-- ============================================================================ +-- Version: 1.0.0 +-- Fecha: 2026-01-04 +-- Database: PostgreSQL 15+ con extension pgvector +-- EPIC: EPIC-013 +-- ============================================================================ + +-- ============================================================================ +-- EXTENSIONES REQUERIDAS +-- ============================================================================ + +CREATE EXTENSION IF NOT EXISTS vector; -- Para embeddings y busqueda semantica +CREATE EXTENSION IF NOT EXISTS pg_trgm; -- Para busqueda fuzzy de texto + +-- ============================================================================ +-- TABLA: documents +-- ============================================================================ +-- Almacena documentos indexados del workspace + +CREATE TABLE IF NOT EXISTS documents ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- Identificacion + path TEXT NOT NULL UNIQUE, -- Ruta relativa al workspace + title TEXT NOT NULL, -- Titulo del documento + + -- Clasificacion + category TEXT NOT NULL, -- orchestration, core, knowledge-base, projects + subcategory TEXT, -- directivas, perfiles, templates, etc. + document_type TEXT NOT NULL, -- directiva, perfil, template, spec, code, guide + project TEXT, -- Proyecto especifico (gamilit, erp-core, etc.) + + -- Contenido + content TEXT NOT NULL, -- Contenido completo del documento + content_hash TEXT NOT NULL, -- Hash para detectar cambios + + -- Metadata + applicable_agents TEXT[] DEFAULT '{}', -- Agentes que deben conocer este doc + metadata JSONB DEFAULT '{}', -- Metadata adicional flexible + frontmatter JSONB, -- Frontmatter parseado (si existe) + + -- Timestamps + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + + -- Soft delete + is_deleted BOOLEAN DEFAULT FALSE, + deleted_at TIMESTAMPTZ +); + +-- Indices para documents +CREATE INDEX idx_documents_path ON documents(path); +CREATE INDEX idx_documents_category ON documents(category); +CREATE INDEX idx_documents_type ON documents(document_type); +CREATE INDEX idx_documents_project ON documents(project); +CREATE INDEX idx_documents_agents ON documents USING GIN(applicable_agents); +CREATE INDEX idx_documents_deleted ON documents(is_deleted) WHERE is_deleted = FALSE; + +-- ============================================================================ +-- TABLA: document_chunks +-- ============================================================================ +-- Chunks de documentos con embeddings para busqueda semantica + +CREATE TABLE IF NOT EXISTS document_chunks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + document_id UUID REFERENCES documents(id) ON DELETE CASCADE, + + -- Posicion + chunk_index INTEGER NOT NULL, -- Orden del chunk en el documento + + -- Contenido + content TEXT NOT NULL, -- Contenido del chunk + heading_path TEXT[] DEFAULT '{}', -- Jerarquia de headings (## Seccion > ### Subseccion) + + -- Ubicacion en archivo original + line_start INTEGER, -- Linea de inicio + line_end INTEGER, -- Linea de fin + + -- Embedding + embedding vector(1536), -- Vector de embedding (OpenAI ada-002) + + -- Timestamps + created_at TIMESTAMPTZ DEFAULT NOW(), + + -- Constraint de unicidad + UNIQUE(document_id, chunk_index) +); + +-- Indice IVFFlat para busqueda por similitud coseno +-- lists = 100 es un buen balance para ~10k-100k chunks +CREATE INDEX idx_chunks_embedding ON document_chunks + USING ivfflat (embedding vector_cosine_ops) + WITH (lists = 100); + +CREATE INDEX idx_chunks_document ON document_chunks(document_id); +CREATE INDEX idx_chunks_heading ON document_chunks USING GIN(heading_path); + +-- ============================================================================ +-- TABLA: document_relations +-- ============================================================================ +-- Relaciones entre documentos (grafo de dependencias) + +CREATE TABLE IF NOT EXISTS document_relations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- Documentos relacionados + source_document_id UUID REFERENCES documents(id) ON DELETE CASCADE, + target_document_id UUID REFERENCES documents(id) ON DELETE CASCADE, + + -- Tipo de relacion + relation_type TEXT NOT NULL, -- references, extends, implements, uses, etc. + context TEXT, -- Contexto adicional de la relacion + + -- Metadata + auto_detected BOOLEAN DEFAULT FALSE, -- True si fue detectado automaticamente + + -- Timestamps + created_at TIMESTAMPTZ DEFAULT NOW(), + + -- Constraint para evitar duplicados + UNIQUE(source_document_id, target_document_id, relation_type) +); + +-- Indices para relaciones +CREATE INDEX idx_relations_source ON document_relations(source_document_id); +CREATE INDEX idx_relations_target ON document_relations(target_document_id); +CREATE INDEX idx_relations_type ON document_relations(relation_type); + +-- ============================================================================ +-- TABLA: code_references +-- ============================================================================ +-- Referencias a codigo encontradas en documentos + +CREATE TABLE IF NOT EXISTS code_references ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + document_id UUID REFERENCES documents(id) ON DELETE CASCADE, + + -- Identificacion del codigo + code_path TEXT NOT NULL, -- Ruta al archivo de codigo + code_name TEXT NOT NULL, -- Nombre (funcion, clase, etc.) + code_type TEXT NOT NULL, -- function, class, interface, const, type + + -- Metadata + language TEXT, -- typescript, javascript, sql, etc. + line_start INTEGER, -- Linea de inicio en codigo + line_end INTEGER, -- Linea de fin + context TEXT, -- Contexto de la referencia + + -- Timestamps + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Indices para referencias de codigo +CREATE INDEX idx_code_refs_document ON code_references(document_id); +CREATE INDEX idx_code_refs_path ON code_references(code_path); +CREATE INDEX idx_code_refs_name ON code_references(code_name); +CREATE INDEX idx_code_refs_type ON code_references(code_type); + +-- ============================================================================ +-- TABLA: sync_log +-- ============================================================================ +-- Log de sincronizacion para tracking + +CREATE TABLE IF NOT EXISTS sync_log ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- Operacion + operation TEXT NOT NULL, -- index, update, delete, sync_category + document_path TEXT, -- Ruta del documento (si aplica) + category TEXT, -- Categoria (si aplica) + + -- Resultado + status TEXT NOT NULL, -- success, error, skipped + message TEXT, -- Mensaje de resultado + chunks_processed INTEGER DEFAULT 0, -- Chunks procesados + duration_ms INTEGER, -- Duracion en milisegundos + + -- Timestamps + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX idx_sync_log_created ON sync_log(created_at DESC); +CREATE INDEX idx_sync_log_status ON sync_log(status); + +-- ============================================================================ +-- FUNCIONES DE BUSQUEDA +-- ============================================================================ + +-- Busqueda semantica principal +CREATE OR REPLACE FUNCTION search_knowledge( + query_embedding vector(1536), + p_category TEXT DEFAULT NULL, + p_document_type TEXT DEFAULT NULL, + p_project TEXT DEFAULT NULL, + p_agent TEXT DEFAULT NULL, + p_threshold FLOAT DEFAULT 0.7, + p_limit INTEGER DEFAULT 10 +) +RETURNS TABLE ( + document_id UUID, + chunk_id UUID, + path TEXT, + title TEXT, + category TEXT, + document_type TEXT, + chunk_content TEXT, + heading_path TEXT[], + line_start INTEGER, + line_end INTEGER, + similarity FLOAT, + applicable_agents TEXT[] +) +LANGUAGE plpgsql +AS $$ +BEGIN + RETURN QUERY + SELECT + d.id as document_id, + c.id as chunk_id, + d.path, + d.title, + d.category, + d.document_type, + c.content as chunk_content, + c.heading_path, + c.line_start, + c.line_end, + 1 - (c.embedding <=> query_embedding) as similarity, + d.applicable_agents + FROM document_chunks c + JOIN documents d ON d.id = c.document_id + WHERE + d.is_deleted = FALSE + AND (p_category IS NULL OR d.category = p_category) + AND (p_document_type IS NULL OR d.document_type = p_document_type) + AND (p_project IS NULL OR d.project = p_project) + AND (p_agent IS NULL OR p_agent = ANY(d.applicable_agents)) + AND 1 - (c.embedding <=> query_embedding) > p_threshold + ORDER BY c.embedding <=> query_embedding + LIMIT p_limit; +END; +$$; + +-- Obtener documentos relacionados (recursivo) +CREATE OR REPLACE FUNCTION get_related_documents( + p_document_id UUID, + p_relation_types TEXT[] DEFAULT NULL, + p_depth INTEGER DEFAULT 1 +) +RETURNS TABLE ( + document_id UUID, + path TEXT, + title TEXT, + relation_type TEXT, + relation_depth INTEGER, + relation_context TEXT +) +LANGUAGE plpgsql +AS $$ +BEGIN + RETURN QUERY + WITH RECURSIVE related AS ( + -- Nivel 0: documento inicial + SELECT + d.id, + d.path, + d.title, + NULL::TEXT as rel_type, + 0 as depth, + NULL::TEXT as context + FROM documents d + WHERE d.id = p_document_id + + UNION ALL + + -- Niveles siguientes + SELECT + d.id, + d.path, + d.title, + r.relation_type, + related.depth + 1, + r.context + FROM related + JOIN document_relations r ON r.source_document_id = related.id + JOIN documents d ON d.id = r.target_document_id + WHERE + related.depth < p_depth + AND d.is_deleted = FALSE + AND (p_relation_types IS NULL OR r.relation_type = ANY(p_relation_types)) + ) + SELECT + related.id as document_id, + related.path, + related.title, + related.rel_type as relation_type, + related.depth as relation_depth, + related.context as relation_context + FROM related + WHERE related.depth > 0 + ORDER BY related.depth, related.title; +END; +$$; + +-- Trazar referencia (para evitar alucinaciones) +CREATE OR REPLACE FUNCTION trace_reference( + p_query TEXT, + p_embedding vector(1536) +) +RETURNS TABLE ( + source_type TEXT, + source_path TEXT, + source_title TEXT, + line_reference TEXT, + content_snippet TEXT, + confidence FLOAT, + related_documents JSONB +) +LANGUAGE plpgsql +AS $$ +BEGIN + RETURN QUERY + WITH best_matches AS ( + SELECT + d.id, + d.path, + d.title, + d.category as src_type, + c.line_start, + c.line_end, + c.content, + 1 - (c.embedding <=> p_embedding) as sim + FROM document_chunks c + JOIN documents d ON d.id = c.document_id + WHERE + d.is_deleted = FALSE + AND 1 - (c.embedding <=> p_embedding) > 0.75 + ORDER BY c.embedding <=> p_embedding + LIMIT 5 + ) + SELECT + bm.src_type as source_type, + bm.path as source_path, + bm.title as source_title, + bm.path || ':' || bm.line_start || '-' || bm.line_end as line_reference, + LEFT(bm.content, 500) as content_snippet, + bm.sim as confidence, + ( + SELECT jsonb_agg(jsonb_build_object( + 'path', d.path, + 'title', d.title, + 'relation', r.relation_type + )) + FROM document_relations r + JOIN documents d ON d.id = r.target_document_id + WHERE r.source_document_id = bm.id + LIMIT 5 + ) as related_documents + FROM best_matches bm; +END; +$$; + +-- ============================================================================ +-- TRIGGERS +-- ============================================================================ + +-- Actualizar updated_at automaticamente +CREATE OR REPLACE FUNCTION update_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trigger_documents_updated_at + BEFORE UPDATE ON documents + FOR EACH ROW + EXECUTE FUNCTION update_updated_at(); + +-- ============================================================================ +-- VISTAS UTILES +-- ============================================================================ + +-- Vista de documentos con estadisticas +CREATE OR REPLACE VIEW v_document_stats AS +SELECT + d.id, + d.path, + d.title, + d.category, + d.document_type, + d.project, + COUNT(c.id) as chunk_count, + d.created_at, + d.updated_at +FROM documents d +LEFT JOIN document_chunks c ON c.document_id = d.id +WHERE d.is_deleted = FALSE +GROUP BY d.id; + +-- Vista de estado de sincronizacion por categoria +CREATE OR REPLACE VIEW v_sync_status AS +SELECT + category, + COUNT(*) as total_documents, + MAX(updated_at) as last_updated, + COUNT(CASE WHEN updated_at < NOW() - INTERVAL '1 hour' THEN 1 END) as stale_count +FROM documents +WHERE is_deleted = FALSE +GROUP BY category; + +-- ============================================================================ +-- COMENTARIOS +-- ============================================================================ + +COMMENT ON TABLE documents IS 'Documentos indexados del workspace para busqueda RAG'; +COMMENT ON TABLE document_chunks IS 'Chunks de documentos con embeddings para busqueda semantica'; +COMMENT ON TABLE document_relations IS 'Grafo de relaciones entre documentos'; +COMMENT ON TABLE code_references IS 'Referencias a codigo encontradas en documentos'; +COMMENT ON TABLE sync_log IS 'Log de operaciones de sincronizacion'; + +COMMENT ON FUNCTION search_knowledge IS 'Busqueda semantica principal con filtros'; +COMMENT ON FUNCTION get_related_documents IS 'Obtiene grafo de documentos relacionados recursivamente'; +COMMENT ON FUNCTION trace_reference IS 'Verifica origen de informacion para evitar alucinaciones'; + +-- ============================================================================ +-- FIN DEL SCHEMA +-- ============================================================================ diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/docs/MCP-TOOLS-SPEC.md b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/docs/MCP-TOOLS-SPEC.md new file mode 100644 index 000000000..b09d8f334 --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/docs/MCP-TOOLS-SPEC.md @@ -0,0 +1,687 @@ +# MCP-TOOLS-SPEC: Especificación de Herramientas RAG + +**Version:** 1.0.0 +**Fecha:** 2026-01-04 +**MCP Server:** mcp-rag-knowledge +**EPIC:** EPIC-013 + +--- + +## RESUMEN + +Este documento especifica las 12 herramientas MCP del sistema RAG para consulta y gestión del conocimiento del workspace. + +--- + +## HERRAMIENTAS DE CONSULTA SEMÁNTICA + +### 1. rag_query_context + +**Descripción:** Busqueda semántica principal sobre el conocimiento del workspace. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| query | string | Sí | Pregunta o consulta en lenguaje natural | +| category | string | No | Filtrar por categoría (orchestration, core, knowledge-base, projects) | +| document_type | string | No | Filtrar por tipo (directiva, perfil, template, code, spec, guide) | +| project | string | No | Filtrar por proyecto específico | +| agent | string | No | Filtrar documentos aplicables a un agente específico | +| threshold | float | No | Umbral mínimo de similitud (default: 0.7) | +| limit | integer | No | Máximo de resultados (default: 10) | + +**Retorno:** + +```typescript +interface QueryResult { + results: Array<{ + document_id: string; + chunk_id: string; + path: string; + title: string; + category: string; + document_type: string; + content: string; + heading_path: string[]; + line_start: number; + line_end: number; + similarity: number; + applicable_agents: string[]; + }>; + query_time_ms: number; + total_matches: number; +} +``` + +**Ejemplo:** + +```typescript +const result = await rag_query_context({ + query: "¿Cómo se debe documentar un cambio según SIMCO?", + category: "orchestration", + threshold: 0.75, + limit: 5 +}); + +// Resultado esperado: +// { +// results: [{ +// path: "orchestration/directivas/simco/SIMCO-DOCUMENTAR.md", +// title: "SIMCO-DOCUMENTAR", +// similarity: 0.89, +// content: "## Proceso de Documentación...", +// line_start: 45, +// line_end: 78 +// }], +// query_time_ms: 120, +// total_matches: 3 +// } +``` + +**Errores Comunes:** + +| Código | Mensaje | Solución | +|--------|---------|----------| +| 400 | "Query too short" | Proporcionar query de al menos 3 palabras | +| 404 | "No results found" | Reducir threshold o generalizar query | +| 503 | "Embedding service unavailable" | Reintentar después de unos segundos | + +--- + +### 2. rag_get_directive + +**Descripción:** Obtiene una directiva SIMCO específica por su identificador. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| directive_id | string | Sí | Identificador de la directiva (ej: "SIMCO-TAREA") | +| include_relations | boolean | No | Incluir documentos relacionados (default: false) | + +**Retorno:** + +```typescript +interface DirectiveResult { + found: boolean; + directive: { + id: string; + path: string; + title: string; + version: string; + priority: string; + applies_to: string; + content: string; + sections: Array<{ + heading: string; + content: string; + line_start: number; + line_end: number; + }>; + checklist: string[]; + }; + relations?: Array<{ + path: string; + title: string; + relation_type: string; + }>; +} +``` + +**Ejemplo:** + +```typescript +const directive = await rag_get_directive({ + directive_id: "SIMCO-RAG", + include_relations: true +}); +``` + +--- + +### 3. rag_get_agent_profile + +**Descripción:** Carga el perfil completo de un agente para inicialización. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| agent_id | string | Sí | Identificador del agente (ej: "PERFIL_BACKEND_DEVELOPER") | +| include_directives | boolean | No | Incluir directivas aplicables (default: true) | +| include_tools | boolean | No | Incluir herramientas MCP disponibles (default: true) | + +**Retorno:** + +```typescript +interface AgentProfileResult { + found: boolean; + profile: { + id: string; + name: string; + system: string; + context_level: string; + responsibilities: string[]; + applicable_directives: string[]; + mcp_tools: string[]; + constraints: string[]; + full_content: string; + }; + directives?: DirectiveResult[]; + tools?: ToolSpec[]; +} +``` + +**Ejemplo:** + +```typescript +const profile = await rag_get_agent_profile({ + agent_id: "PERFIL_MCP_DEVELOPER", + include_directives: true, + include_tools: true +}); +``` + +--- + +## HERRAMIENTAS DE TRAZABILIDAD + +### 4. rag_trace_reference + +**Descripción:** Verifica el origen de una afirmación para prevenir alucinaciones. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| claim | string | Sí | Afirmación a verificar | +| context | string | No | Contexto adicional para la búsqueda | +| min_confidence | float | No | Confianza mínima requerida (default: 0.75) | + +**Retorno:** + +```typescript +interface TraceResult { + verified: boolean; + sources: Array<{ + source_type: string; + source_path: string; + source_title: string; + line_reference: string; // formato: "path:line_start-line_end" + content_snippet: string; + confidence: number; + related_documents: Array<{ + path: string; + title: string; + relation: string; + }>; + }>; + recommendation: "cite" | "verify" | "cannot_confirm"; +} +``` + +**Ejemplo:** + +```typescript +const trace = await rag_trace_reference({ + claim: "Las directivas SIMCO son obligatorias para todos los agentes", + min_confidence: 0.8 +}); + +// Si verified = true, usar las fuentes para citar +// Si verified = false, indicar que no se puede confirmar +``` + +--- + +### 5. rag_get_relations + +**Descripción:** Obtiene el grafo de relaciones de un documento. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| document_path | string | Sí | Ruta del documento | +| relation_types | string[] | No | Filtrar por tipos de relación | +| depth | integer | No | Profundidad de recursión (default: 1, max: 3) | +| direction | string | No | "outgoing" \| "incoming" \| "both" (default: "both") | + +**Retorno:** + +```typescript +interface RelationsResult { + document: { + id: string; + path: string; + title: string; + }; + relations: Array<{ + document_id: string; + path: string; + title: string; + relation_type: string; + relation_depth: number; + direction: "outgoing" | "incoming"; + context: string; + }>; + graph_summary: { + total_relations: number; + by_type: Record; + max_depth_reached: number; + }; +} +``` + +**Tipos de Relación:** + +| Tipo | Descripción | +|------|-------------| +| references | Documento A menciona/cita a documento B | +| extends | Documento A extiende/amplía documento B | +| implements | Documento A implementa especificación B | +| uses | Documento A usa/depende de documento B | +| supersedes | Documento A reemplaza documento B | + +--- + +### 6. rag_find_code + +**Descripción:** Busca referencias de código en el workspace. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| name | string | No | Nombre de función/clase/tipo a buscar | +| code_type | string | No | Tipo: function, class, interface, const, type | +| language | string | No | Lenguaje: typescript, javascript, sql, python | +| path_pattern | string | No | Patrón glob para filtrar rutas | +| include_context | boolean | No | Incluir código circundante (default: true) | + +**Retorno:** + +```typescript +interface CodeSearchResult { + matches: Array<{ + path: string; + name: string; + code_type: string; + language: string; + line_start: number; + line_end: number; + signature: string; + documentation: string; + code_snippet: string; + related_documents: string[]; // Documentos que referencian este código + }>; + total_matches: number; +} +``` + +**Ejemplo:** + +```typescript +const code = await rag_find_code({ + name: "validateParams", + code_type: "function", + language: "typescript" +}); +``` + +--- + +### 7. rag_explain_impact + +**Descripción:** Analiza el impacto de modificar un documento. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| document_path | string | Sí | Ruta del documento a analizar | +| change_type | string | Sí | "create" \| "modify" \| "delete" | +| affected_sections | string[] | No | Secciones específicas afectadas | + +**Retorno:** + +```typescript +interface ImpactAnalysis { + document: { + path: string; + title: string; + category: string; + document_type: string; + }; + impact: { + direct_dependents: Array<{ + path: string; + title: string; + relation_type: string; + impact_level: "high" | "medium" | "low"; + }>; + indirect_dependents: Array<{ + path: string; + title: string; + distance: number; + impact_level: "high" | "medium" | "low"; + }>; + agents_affected: string[]; + risk_level: "critical" | "high" | "medium" | "low"; + propagation_order: string[]; // Orden sugerido para actualizar + }; + recommendations: string[]; +} +``` + +**Ejemplo:** + +```typescript +const impact = await rag_explain_impact({ + document_path: "orchestration/directivas/simco/SIMCO-TAREA.md", + change_type: "modify", + affected_sections: ["PRINCIPIO FUNDAMENTAL"] +}); + +// Antes de hacer cambios significativos, revisar: +// - impact.risk_level +// - impact.agents_affected +// - impact.propagation_order +``` + +--- + +## HERRAMIENTAS DE INDEXACIÓN + +### 8. rag_index_document + +**Descripción:** Indexa o re-indexa un documento en el sistema RAG. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| path | string | Sí | Ruta del documento a indexar | +| force | boolean | No | Forzar re-indexación aunque no haya cambios (default: false) | +| extract_relations | boolean | No | Detectar y crear relaciones automáticamente (default: true) | + +**Retorno:** + +```typescript +interface IndexResult { + success: boolean; + document: { + id: string; + path: string; + title: string; + category: string; + document_type: string; + }; + indexing: { + chunks_created: number; + relations_detected: number; + code_references_found: number; + processing_time_ms: number; + }; + status: "created" | "updated" | "unchanged" | "error"; + error?: string; +} +``` + +**Ejemplo:** + +```typescript +// Después de crear o modificar un documento +const result = await rag_index_document({ + path: "orchestration/directivas/simco/SIMCO-NUEVA.md", + extract_relations: true +}); + +if (result.success) { + console.log(`Indexado: ${result.indexing.chunks_created} chunks`); +} else { + console.error(`Error: ${result.error}`); +} +``` + +--- + +### 9. rag_sync_category + +**Descripción:** Sincroniza todos los documentos de una categoría. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| category | string | Sí | Categoría a sincronizar | +| subcategory | string | No | Subcategoría específica | +| force | boolean | No | Forzar re-indexación completa (default: false) | +| dry_run | boolean | No | Solo simular, no hacer cambios (default: false) | + +**Retorno:** + +```typescript +interface SyncResult { + category: string; + subcategory?: string; + summary: { + total_files: number; + indexed: number; + updated: number; + unchanged: number; + deleted: number; + errors: number; + }; + details: Array<{ + path: string; + status: "indexed" | "updated" | "unchanged" | "deleted" | "error"; + chunks: number; + error?: string; + }>; + duration_ms: number; +} +``` + +**Ejemplo:** + +```typescript +// Sincronizar todas las directivas +const sync = await rag_sync_category({ + category: "orchestration", + subcategory: "directivas", + dry_run: false +}); + +console.log(`Sincronizado: ${sync.summary.indexed} nuevos, ${sync.summary.updated} actualizados`); +``` + +--- + +### 10. rag_get_sync_status + +**Descripción:** Obtiene el estado de sincronización del sistema. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| category | string | No | Filtrar por categoría | +| include_stale | boolean | No | Incluir documentos desactualizados (default: true) | + +**Retorno:** + +```typescript +interface SyncStatus { + overall: { + total_documents: number; + total_chunks: number; + last_sync: string; // ISO timestamp + health: "healthy" | "degraded" | "unhealthy"; + }; + by_category: Array<{ + category: string; + total_documents: number; + last_updated: string; + stale_count: number; + stale_documents?: string[]; + }>; + pending_sync: Array<{ + path: string; + reason: "new" | "modified" | "deleted"; + detected_at: string; + }>; +} +``` + +--- + +## HERRAMIENTAS DE VALIDACIÓN + +### 11. rag_validate_coverage + +**Descripción:** Verifica la cobertura de indexación del workspace. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| category | string | No | Categoría a validar (todas si no se especifica) | +| report_missing | boolean | No | Incluir lista de archivos no indexados (default: true) | + +**Retorno:** + +```typescript +interface CoverageReport { + summary: { + total_files_expected: number; + total_files_indexed: number; + coverage_percentage: number; + health: "complete" | "partial" | "incomplete"; + }; + by_category: Array<{ + category: string; + expected: number; + indexed: number; + coverage: number; + }>; + missing: Array<{ + path: string; + category: string; + reason: "not_indexed" | "outdated" | "excluded"; + }>; + recommendations: string[]; +} +``` + +**Ejemplo:** + +```typescript +const coverage = await rag_validate_coverage({ + category: "orchestration", + report_missing: true +}); + +if (coverage.summary.coverage_percentage < 100) { + console.log("Documentos faltantes:", coverage.missing); +} +``` + +--- + +### 12. rag_report_feedback + +**Descripción:** Reporta problemas de calidad en el sistema RAG. + +**Parámetros:** + +| Nombre | Tipo | Requerido | Descripción | +|--------|------|-----------|-------------| +| feedback_type | string | Sí | "missing_info" \| "incorrect_info" \| "outdated" \| "low_relevance" | +| query | string | Sí | Query que generó el problema | +| context | string | No | Contexto adicional del problema | +| document_path | string | No | Documento específico afectado | +| expected_result | string | No | Qué se esperaba encontrar | + +**Retorno:** + +```typescript +interface FeedbackResult { + feedback_id: string; + received: boolean; + suggested_actions: Array<{ + action: string; + priority: "high" | "medium" | "low"; + automated: boolean; + }>; +} +``` + +**Ejemplo:** + +```typescript +// Cuando una búsqueda no encuentra lo esperado +const feedback = await rag_report_feedback({ + feedback_type: "missing_info", + query: "¿Cómo configurar hooks en NEXUS?", + expected_result: "Debería encontrar SIMCO-HOOKS pero no está indexado", + document_path: "orchestration/directivas/simco/SIMCO-HOOKS.md" +}); +``` + +--- + +## SCHEMAS JSON PARA REGISTRO MCP + +```typescript +// schemas/tools.ts +export const toolSchemas = { + rag_query_context: { + name: "rag_query_context", + description: "Búsqueda semántica en el conocimiento del workspace", + parameters: { + type: "object", + properties: { + query: { type: "string", description: "Consulta en lenguaje natural" }, + category: { type: "string", enum: ["orchestration", "core", "knowledge-base", "projects"] }, + document_type: { type: "string", enum: ["directiva", "perfil", "template", "code", "spec", "guide"] }, + project: { type: "string" }, + agent: { type: "string" }, + threshold: { type: "number", default: 0.7 }, + limit: { type: "integer", default: 10 } + }, + required: ["query"] + } + }, + // ... resto de schemas +}; +``` + +--- + +## NOTAS DE IMPLEMENTACIÓN + +### Manejo de Errores + +Todas las herramientas deben manejar: + +1. **Errores de conexión:** Reintentar con backoff exponencial +2. **Errores de embedding:** Cachear embeddings para evitar recálculos +3. **Documentos no encontrados:** Retornar resultado vacío, no error + +### Rate Limiting + +- Embeddings: Máximo 100 requests/minuto a OpenAI +- Queries: Sin límite interno (depende de PostgreSQL) +- Sync: Máximo 1 sync completo por minuto + +### Caché + +- Embeddings: Cache de 7 días en PostgreSQL +- Queries: Cache de 5 minutos para queries idénticos +- Perfiles: Cache en memoria durante sesión + +--- + +**Version:** 1.0.0 | **EPIC:** EPIC-013 | **Sistema:** SIMCO diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/orchestration/00-guidelines/CONTEXTO-PROYECTO.md b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/orchestration/00-guidelines/CONTEXTO-PROYECTO.md new file mode 100644 index 000000000..12925d670 --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/orchestration/00-guidelines/CONTEXTO-PROYECTO.md @@ -0,0 +1,83 @@ +# CONTEXTO-PROYECTO: MCP {NOMBRE} + +**Version:** 0.1.0 +**Fecha:** {FECHA} +**Sistema:** NEXUS v3.4 + SIMCO + +--- + +## IDENTIFICACION + +```yaml +proyecto: "mcp-{nombre}" +tipo: "mcp-server-interno" +estado: "development" +prioridad: "{alta|maxima}" + +ubicacion: + workspace: "/home/isem/workspace-v1" + proyecto: "core/mcp-servers/internal/{nombre}" + repositorio: "git@gitea-server:rckrdmrd/mcp-{nombre}.git" + +stack: + runtime: "Node.js >= 18" + lenguaje: "TypeScript" + database: "PostgreSQL + pgvector" +``` + +--- + +## PROPOSITO + +{DESCRIPCION_DETALLADA_DEL_PROPOSITO} + +--- + +## HERRAMIENTAS MCP + +| Herramienta | Descripcion | Estado | +|-------------|-------------|--------| +| `{tool_1}` | {descripcion} | planned | +| `{tool_2}` | {descripcion} | planned | + +--- + +## DEPENDENCIAS + +### Externas +- PostgreSQL >= 15 con extension pgvector +- OpenAI API (para embeddings) + +### Del Workspace +- Acceso a documentacion en orchestration/ +- Acceso a knowledge-base/ + +--- + +## DIRECTIVAS APLICABLES + +```yaml +siempre: + - @SIMCO_MCP + - @PRINCIPIO_CAPVED + - @PRINCIPIO_DOC_PRIMERO + +operaciones: + crear_herramienta: [@SIMCO_CREAR] + modificar: [@SIMCO_MODIFICAR] + validar: [@SIMCO_VALIDAR] +``` + +--- + +## PERFILES RELACIONADOS + +| Perfil | Responsabilidad | +|--------|-----------------| +| @PERFIL_MCP_DEVELOPER | Desarrollo de este MCP | +| @PERFIL_RAG_ENGINEER | Integracion con RAG | +| @PERFIL_MCP_ARCHITECT | Diseno y arquitectura | + +--- + +**Contexto generado:** {FECHA} diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/package.json.template b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/package.json.template new file mode 100644 index 000000000..7377311d9 --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/package.json.template @@ -0,0 +1,50 @@ +{ + "name": "mcp-{nombre}", + "version": "0.1.0", + "description": "{DESCRIPCION}", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "ts-node-dev --respawn src/index.ts", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", + "lint": "eslint src/**/*.ts", + "lint:fix": "eslint src/**/*.ts --fix", + "typecheck": "tsc --noEmit", + "health-check": "curl -s http://localhost:${PORT:-3100}/health || echo 'Server not running'" + }, + "keywords": [ + "mcp", + "model-context-protocol", + "anthropic", + "claude" + ], + "author": "workspace-v1", + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "^0.25.0", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "pg": "^8.11.3", + "pgvector": "^0.1.8" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/jest": "^29.5.11", + "@types/node": "^20.10.0", + "@types/pg": "^8.10.9", + "@typescript-eslint/eslint-plugin": "^6.13.0", + "@typescript-eslint/parser": "^6.13.0", + "eslint": "^8.54.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "ts-node-dev": "^2.0.0", + "typescript": "^5.3.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/src/index.ts.template b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/src/index.ts.template new file mode 100644 index 000000000..943bc428d --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/src/index.ts.template @@ -0,0 +1,61 @@ +/** + * MCP Server: {NOMBRE} + * + * {DESCRIPCION} + * + * @version 0.1.0 + */ + +import express from 'express'; +import dotenv from 'dotenv'; + +// Load environment variables +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 3100; + +// Middleware +app.use(express.json()); + +// Health check endpoint +app.get('/health', (req, res) => { + res.json({ + status: 'ok', + service: 'mcp-{nombre}', + version: '0.1.0', + timestamp: new Date().toISOString(), + }); +}); + +// MCP Tools endpoint +app.post('/tools/:toolName', async (req, res) => { + const { toolName } = req.params; + const { parameters } = req.body; + + try { + // TODO: Implement tool routing + const result = await handleTool(toolName, parameters); + res.json({ success: true, result }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } +}); + +// Tool handler +async function handleTool(toolName: string, parameters: unknown): Promise { + switch (toolName) { + // TODO: Add tool cases + default: + throw new Error(`Unknown tool: ${toolName}`); + } +} + +// Start server +app.listen(PORT, () => { + console.log(`MCP Server {nombre} running on port ${PORT}`); + console.log(`Health check: http://localhost:${PORT}/health`); +}); diff --git a/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/tests/.gitkeep b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/tests/.gitkeep new file mode 100644 index 000000000..3ffdcb7af --- /dev/null +++ b/core/mcp-servers/templates/TEMPLATE-MCP-INTERNO/tests/.gitkeep @@ -0,0 +1 @@ +# Tests directory diff --git a/core/orchestration/README.md b/core/orchestration/README.md index 67ed47c9f..7a10bd1df 100644 --- a/core/orchestration/README.md +++ b/core/orchestration/README.md @@ -27,7 +27,7 @@ Este directorio contiene el **Sistema NEXUS** de orquestación de agentes IA par ### Acceso Rápido ``` -core/catalog/ # 🆕 CATÁLOGO DE FUNCIONALIDADES (CONSULTAR PRIMERO) +shared/catalog/ # 🆕 CATÁLOGO DE FUNCIONALIDADES (CONSULTAR PRIMERO) ├── CATALOG-INDEX.yml # Índice máquina-readable ├── auth/ # Autenticación y autorización ├── session-management/ # Gestión de sesiones @@ -102,8 +102,8 @@ referencias/ @TPL_CAPVED: templates/TEMPLATE-TAREA-CAPVED.md # CATÁLOGO DE FUNCIONALIDADES (CONSULTAR PRIMERO) -@CATALOG: core/catalog/ -@CATALOG_INDEX: core/catalog/CATALOG-INDEX.yml +@CATALOG: shared/catalog/ +@CATALOG_INDEX: shared/catalog/CATALOG-INDEX.yml # Operaciones @REUTILIZAR: directivas/simco/SIMCO-REUTILIZAR.md diff --git a/core/orchestration/agents/legacy/LEGACY-NOTICE.md b/core/orchestration/agents/legacy/LEGACY-NOTICE.md index 1375b13b9..212a14e56 100644 --- a/core/orchestration/agents/legacy/LEGACY-NOTICE.md +++ b/core/orchestration/agents/legacy/LEGACY-NOTICE.md @@ -46,7 +46,7 @@ core/orchestration/ │ ├── SIMCO-BACKEND.md │ └── ... │ -└── core/catalog/ # 🆕 CATÁLOGO DE FUNCIONALIDADES +└── shared/catalog/ # 🆕 CATÁLOGO DE FUNCIONALIDADES ├── CATALOG-INDEX.yml # Índice para búsqueda ├── auth/ ├── session-management/ @@ -138,7 +138,7 @@ Los archivos `INIT-NEXUS-*.md` siguen siendo útiles como: - **Índice SIMCO:** core/orchestration/directivas/simco/_INDEX.md - **Perfiles de Agentes:** core/orchestration/agents/perfiles/ -- **Catálogo:** core/catalog/ +- **Catálogo:** shared/catalog/ - **Aliases:** core/orchestration/referencias/ALIASES.yml --- diff --git a/core/orchestration/agents/legacy/README.md b/core/orchestration/agents/legacy/README.md index 9fc423635..6724a814b 100644 --- a/core/orchestration/agents/legacy/README.md +++ b/core/orchestration/agents/legacy/README.md @@ -33,7 +33,7 @@ core/orchestration/ │ ├── SIMCO-MODIFICAR.md │ └── SIMCO-{DOMINIO}.md │ -└── core/catalog/ # Funcionalidades reutilizables +└── shared/catalog/ # Funcionalidades reutilizables ├── auth/ ├── notifications/ └── ... diff --git a/core/orchestration/agents/perfiles/PERFIL-KB-MANAGER.md b/core/orchestration/agents/perfiles/PERFIL-KB-MANAGER.md index 125164c5c..6fd2a9b7b 100644 --- a/core/orchestration/agents/perfiles/PERFIL-KB-MANAGER.md +++ b/core/orchestration/agents/perfiles/PERFIL-KB-MANAGER.md @@ -2,9 +2,10 @@ **ID:** @PERFIL_KB_MANAGER **Nombre:** Knowledge-Base Manager -**Version:** 1.0.0 +**Version:** 1.1.0 **Sistema:** NEXUS v3.4 + SIMCO + CAPVED **Creado:** 2026-01-04 +**Actualizado:** 2026-01-04 **EPIC:** EPIC-012 --- @@ -12,7 +13,7 @@ ## ROL Gestor de la base de conocimiento compartida del workspace, responsable de: -- Coordinar mejoras en `core/catalog/` y `shared/knowledge-base/` +- Coordinar mejoras en `shared/catalog/` y `shared/knowledge-base/` - Analizar mejoras propagables siguiendo proceso CAPVED - Detectar dependencias internas entre modulos - Generar tareas SCRUM formales para propagacion @@ -30,7 +31,7 @@ Gestor de la base de conocimiento compartida del workspace, responsable de: - C: Contexto del modulo afectado - A: Analisis de impacto en KB y proyectos consumidores - P: Plan de propagacion por niveles -3. **Actualizar core/catalog** si la mejora aplica a modulos base +3. **Actualizar shared/catalog** si la mejora aplica a modulos base 4. **Actualizar shared/knowledge-base** con cambios sincronizados 5. **Generar tareas SCRUM** para proyectos afectados usando `generate-scrum-tasks.sh` 6. **Coordinar con @PERFIL_PROJECT_AGENT** para ejecucion en proyectos @@ -101,7 +102,7 @@ Scripts disponibles en `devtools/scripts/propagation/`: | v 4. ACTUALIZAR NIVEL 0 (si aplica) - Actualizar modulo en core/catalog/ + Actualizar modulo en shared/catalog/ Validar: Documentacion actualizada | v @@ -169,6 +170,9 @@ Scripts disponibles en `devtools/scripts/propagation/`: | @PERFIL_DOCUMENTATION | Colabora en documentacion de cambios | Seccion en TASK | | @PERFIL_INTEGRATION_VALIDATOR | Valida integracion final | Checklist de validacion | | @PERFIL_TECH_LEADER | Escala decisiones arquitecturales | Issue/Reunion | +| @PERFIL_PROPAGATION_TRACKER | Tracking de estado de propagaciones | TRAZABILIDAD-PROPAGACION.yml | +| @PERFIL_PRODUCTION_MANAGER | Sincroniza cambios con produccion | Registro/Alerta | +| @PERFIL_SECRETS_MANAGER | Coordina cambios de secretos | Protocolo seguro | --- @@ -233,6 +237,13 @@ formato: "Comentario en tarea o seccion EJECUCION" - USAGE.md: `shared/knowledge-base/propagacion/USAGE.md` - USAGE-ORQUESTACION.md: `shared/knowledge-base/propagacion/USAGE-ORQUESTACION.md` +### Perfiles Relacionados + +- @PERFIL_PROPAGATION_TRACKER: `orchestration/agents/perfiles/PERFIL-PROPAGATION-TRACKER.md` +- @PERFIL_PRODUCTION_MANAGER: `orchestration/agents/perfiles/PERFIL-PRODUCTION-MANAGER.md` +- @PERFIL_SECRETS_MANAGER: `orchestration/agents/perfiles/PERFIL-SECRETS-MANAGER.md` +- @PERFIL_MONITORING_AGENT: `orchestration/agents/perfiles/PERFIL-MONITORING-AGENT.md` + --- **Perfil creado por:** EPIC-012 diff --git a/core/orchestration/auditorias/EJECUCION-BLOQUE1-2025-12-12.md b/core/orchestration/auditorias/EJECUCION-BLOQUE1-2025-12-12.md index d02831044..195f6a622 100644 --- a/core/orchestration/auditorias/EJECUCION-BLOQUE1-2025-12-12.md +++ b/core/orchestration/auditorias/EJECUCION-BLOQUE1-2025-12-12.md @@ -45,7 +45,7 @@ grep "Versión:" /home/isem/workspace/core/orchestration/README.md | head -1 **Cambios realizados:** - 131 archivos en `core/orchestration/` corregidos -- 11 archivos en `core/catalog/_reference/` corregidos +- 11 archivos en `shared/catalog/_reference/` corregidos - Archivos de catalog raíz (*.yml, *.md) corregidos **Verificación:** @@ -162,7 +162,7 @@ grep -n "Versión:" ~/workspace/core/orchestration/README.md | head -1 find ~/workspace/core/orchestration -perm 600 -type f | wc -l # Debe ser 0 # Verificar _reference/ poblados -find ~/workspace/core/catalog -name "*.reference.ts" | wc -l # Debe ser 11 +find ~/workspace/shared/catalog -name "*.reference.ts" | wc -l # Debe ser 11 # Verificar UserIdConversionService exportado grep "UserIdConversionService" ~/workspace/projects/gamilit/apps/backend/src/shared/services/index.ts diff --git a/core/orchestration/auditorias/EJECUCION-BLOQUE5-6-2025-12-12.md b/core/orchestration/auditorias/EJECUCION-BLOQUE5-6-2025-12-12.md index 01508182e..223072e39 100644 --- a/core/orchestration/auditorias/EJECUCION-BLOQUE5-6-2025-12-12.md +++ b/core/orchestration/auditorias/EJECUCION-BLOQUE5-6-2025-12-12.md @@ -236,7 +236,7 @@ Sprint 0 P0 - Completado # ERP-Suite shared-libs ls -la ~/workspace/projects/erp-suite/apps/shared-libs/core/entities/ ls -la ~/workspace/projects/erp-suite/apps/shared-libs/core/middleware/ -ls -la ~/workspace/projects/erp-suite/apps/shared-libs/core/constants/ +ls -la ~/workspace/projects/erp-suite/apps/shared-libs/shared/constants/ # Betting Analytics ls -la ~/workspace/projects/betting-analytics/apps/backend/src/ diff --git a/core/orchestration/auditorias/PLAN-AUDITORIA-ARQUITECTONICA-2025-12-12.md b/core/orchestration/auditorias/PLAN-AUDITORIA-ARQUITECTONICA-2025-12-12.md index 1655beb4f..f7b368656 100644 --- a/core/orchestration/auditorias/PLAN-AUDITORIA-ARQUITECTONICA-2025-12-12.md +++ b/core/orchestration/auditorias/PLAN-AUDITORIA-ARQUITECTONICA-2025-12-12.md @@ -78,7 +78,7 @@ core: contenido: Directivas, principios, agentes, templates prioridad: P0 - - nombre: core/catalog + - nombre: shared/catalog nivel: 0 (Global) contenido: Funcionalidades reutilizables prioridad: P0 @@ -334,7 +334,7 @@ herramientas: secuencia: 1_core: - core/orchestration/directivas/ (principios base) - - core/catalog/ (funcionalidades compartidas) + - shared/catalog/ (funcionalidades compartidas) prioridad: PRIMERO (establecer baseline) 2_proyectos_maduros: @@ -698,7 +698,7 @@ protocolo: orden_ejecucion: fase_6a_core: - Correcciones en core/orchestration - - Correcciones en core/catalog + - Correcciones en shared/catalog razon: Base para proyectos fase_6b_proyectos_referencia: diff --git a/core/orchestration/auditorias/PLAN-CORRECCIONES-COMPLETO-2025-12-12.md b/core/orchestration/auditorias/PLAN-CORRECCIONES-COMPLETO-2025-12-12.md index dfa5c2273..bdff52503 100644 --- a/core/orchestration/auditorias/PLAN-CORRECCIONES-COMPLETO-2025-12-12.md +++ b/core/orchestration/auditorias/PLAN-CORRECCIONES-COMPLETO-2025-12-12.md @@ -849,7 +849,7 @@ P0 Completado [ ] core/orchestration versión unificada 3.3 [ ] core/orchestration permisos 644 [ ] core/orchestration 6 principios documentados -[ ] core/catalog _reference/ poblados (8 funcionalidades) +[ ] shared/catalog _reference/ poblados (8 funcionalidades) [ ] gamilit UserIdConversionService creado [ ] gamilit Repository Factory implementado [ ] gamilit God classes divididas (al menos 2) diff --git a/core/orchestration/auditorias/REPORTE-ANALISIS-FASE2-2025-12-12.md b/core/orchestration/auditorias/REPORTE-ANALISIS-FASE2-2025-12-12.md index f86576320..d3cf71193 100644 --- a/core/orchestration/auditorias/REPORTE-ANALISIS-FASE2-2025-12-12.md +++ b/core/orchestration/auditorias/REPORTE-ANALISIS-FASE2-2025-12-12.md @@ -13,7 +13,7 @@ Se completó el análisis exhaustivo de **6 áreas** del workspace: | Área | Estado | Score | Hallazgos P0 | P1 | P2 | |------|--------|-------|--------------|----|----| | **core/orchestration** | 🟡 Necesita mejoras | 7/10 | 3 | 5 | 3 | -| **core/catalog** | 🟡 Necesita mejoras | 8/10 | 1 | 4 | 6 | +| **shared/catalog** | 🟡 Necesita mejoras | 8/10 | 1 | 4 | 6 | | **gamilit** | 🟡 Deuda técnica | 6.5/10 | 4 | 6 | 10 | | **trading-platform** | 🟡 MVP en progreso | 6/10 | 5 | 5 | 4 | | **erp-suite** | 🟡 Duplicación crítica | 5/10 | 3 | 4 | 3 | @@ -123,7 +123,7 @@ Se completó el análisis exhaustivo de **6 áreas** del workspace: | Proyecto | Docs | ADRs | Inventarios | Propagación | |----------|------|------|-------------|-------------| | core/orchestration | ✅ 177 MD | ✅ Sistema | ✅ YAML | 🟡 Parcial | -| core/catalog | ✅ 8 funciones | N/A | ✅ Tracking | ✅ OK | +| shared/catalog | ✅ 8 funciones | N/A | ✅ Tracking | ✅ OK | | gamilit | ✅ 17 carpetas | ✅ 20 ADRs | ✅ Completos | ✅ OK | | trading-platform | ✅ 264 docs | ✅ 14 ADRs | ✅ Completos | ✅ OK | | erp-suite | ✅ 449+ docs | ⚠️ Parcial | 🟡 Parcial | 🟡 Parcial | @@ -134,7 +134,7 @@ Se completó el análisis exhaustivo de **6 áreas** del workspace: ## CÓDIGO DUPLICADO ENTRE PROYECTOS -### Candidatos para core/catalog +### Candidatos para shared/catalog | Funcionalidad | Proyectos | Estado | Acción | |---------------|-----------|--------|--------| @@ -183,7 +183,7 @@ Se completó el análisis exhaustivo de **6 áreas** del workspace: | # | Acción | Proyectos | Esfuerzo | |---|--------|-----------|----------| -| 1 | Poblar `_reference/` en core/catalog | catalog | 8h | +| 1 | Poblar `_reference/` en shared/catalog | catalog | 8h | | 2 | Sincronizar versionado orchestration a 3.3 | core | 2h | | 3 | Cambiar permisos 600→644 en orchestration | core | 1h | | 4 | Crear UserIdConversionService en gamilit | gamilit | 4h | diff --git a/core/orchestration/directivas/DIRECTIVA-REFERENCIAS-PROYECTOS.md b/core/orchestration/directivas/DIRECTIVA-REFERENCIAS-PROYECTOS.md index 7f81c8b80..0109a11d7 100644 --- a/core/orchestration/directivas/DIRECTIVA-REFERENCIAS-PROYECTOS.md +++ b/core/orchestration/directivas/DIRECTIVA-REFERENCIAS-PROYECTOS.md @@ -34,7 +34,7 @@ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ NUNCA referenciar un proyecto desde otro proyecto. │ -│ SIEMPRE usar core/catalog/ o core/orchestration/ como fuente. │ +│ SIEMPRE usar shared/catalog/ o core/orchestration/ como fuente. │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -47,7 +47,7 @@ | Tipo | Ejemplo | Uso Correcto | |------|---------|--------------| -| **Core Catalog** | `core/catalog/auth/` | Componentes reutilizables | +| **Core Catalog** | `shared/catalog/auth/` | Componentes reutilizables | | **Core Orchestration** | `core/orchestration/directivas/` | Directivas y templates | | **Subproyecto Interno** | `erp-suite/apps/erp-core/` | Solo desde la misma suite | | **Ejemplos Ilustrativos** | `ejemplos: "gamilit, trading"` | Solo como ilustración marcada | @@ -70,7 +70,7 @@ JERARQUÍA_VÁLIDA: Nivel_0_Core: - - core/catalog/ # Funcionalidades reutilizables + - shared/catalog/ # Funcionalidades reutilizables - core/orchestration/ # Directivas y templates - core/devtools/ # Herramientas de desarrollo @@ -130,7 +130,7 @@ ANTES: DESPUÉS: # ELIMINAR la línea completamente # O cambiar a: - | Catálogo global | `/home/isem/workspace/core/catalog/` | + | Catálogo global | `/home/isem/workspace/shared/catalog/` | ``` ### Caso 2: Ruta a Otro Proyecto @@ -140,7 +140,7 @@ ANTES: - Patrones auth: `projects/gamilit/apps/backend/src/auth/` DESPUÉS: - - Patrones auth: `core/catalog/auth/` + - Patrones auth: `shared/catalog/auth/` ``` ### Caso 3: Lista de Proyectos de Referencia @@ -153,11 +153,11 @@ ANTES: DESPUÉS: ## Patrones de Referencia (desde Catálogo) - > **Nota:** Usar siempre core/catalog/ para componentes reutilizables. + > **Nota:** Usar siempre shared/catalog/ para componentes reutilizables. | Patrón | Catálogo | - | Auth | core/catalog/auth/ | - | RLS | core/catalog/multi-tenancy/ | + | Auth | shared/catalog/auth/ | + | RLS | shared/catalog/multi-tenancy/ | ``` ### Caso 4: Directiva con Header de Proyecto @@ -247,7 +247,7 @@ VALIDACIÓN_ADICIONAL: ```yaml CONTEXTO_A_PASAR: referencias_permitidas: - - core/catalog/ + - shared/catalog/ - core/orchestration/ - {proyecto_actual}/ # Solo interno referencias_prohibidas: @@ -295,7 +295,7 @@ echo "=== Fin de Auditoría ===" ## REFERENCIAS - **Principio relacionado:** `PRINCIPIO-ANTI-DUPLICACION.md` -- **Catálogo global:** `core/catalog/` +- **Catálogo global:** `shared/catalog/` - **Templates:** `core/orchestration/templates/` - **Índice SIMCO:** `core/orchestration/directivas/simco/_INDEX.md` diff --git a/core/orchestration/directivas/_MAP.md b/core/orchestration/directivas/_MAP.md index 322a48ad4..bb4f1541e 100644 --- a/core/orchestration/directivas/_MAP.md +++ b/core/orchestration/directivas/_MAP.md @@ -12,7 +12,7 @@ **ANTES de implementar funcionalidades comunes**, verificar si existe código probado: ``` -core/catalog/ # FUNCIONALIDADES REUTILIZABLES +shared/catalog/ # FUNCIONALIDADES REUTILIZABLES ├── CATALOG-INDEX.yml # Índice para búsqueda rápida ├── auth/ # Autenticación y autorización ├── session-management/ # Gestión de sesiones diff --git a/core/orchestration/directivas/simco/SIMCO-PROPAGACION-MEJORAS.md b/core/orchestration/directivas/simco/SIMCO-PROPAGACION-MEJORAS.md index 70b607c53..bf6d56417 100644 --- a/core/orchestration/directivas/simco/SIMCO-PROPAGACION-MEJORAS.md +++ b/core/orchestration/directivas/simco/SIMCO-PROPAGACION-MEJORAS.md @@ -361,7 +361,7 @@ Ver: @NIVELES_PROP (`shared/knowledge-base/propagacion/NIVELES-PROPAGACION.yml`) | Nivel | Nombre | Contenido | |-------|--------|-----------| -| 0 | Core Catalog | core/catalog/ | +| 0 | Core Catalog | shared/catalog/ | | 1 | Knowledge-Base | shared/knowledge-base/ | | 2 | Proyectos Base | erp-core, gamilit, trading-platform | | 3 | Proyectos Hoja | Verticales ERP, otros | diff --git a/core/orchestration/referencias/ALIASES.yml b/core/orchestration/referencias/ALIASES.yml index c8a58bbbb..063013ee0 100644 --- a/core/orchestration/referencias/ALIASES.yml +++ b/core/orchestration/referencias/ALIASES.yml @@ -28,16 +28,16 @@ global: # ═══════════════════════════════════════════════════════════════════ # CATÁLOGO DE FUNCIONALIDADES REUTILIZABLES (CONSULTAR PRIMERO) # ═══════════════════════════════════════════════════════════════════ - "@CATALOG": "core/catalog/" - "@CATALOG_INDEX": "core/catalog/CATALOG-INDEX.yml" - "@CATALOG_AUTH": "core/catalog/auth/" - "@CATALOG_SESSION": "core/catalog/session-management/" - "@CATALOG_RATELIMIT": "core/catalog/rate-limiting/" - "@CATALOG_NOTIFY": "core/catalog/notifications/" - "@CATALOG_TENANT": "core/catalog/multi-tenancy/" - "@CATALOG_FLAGS": "core/catalog/feature-flags/" - "@CATALOG_WS": "core/catalog/websocket/" - "@CATALOG_PAYMENTS": "core/catalog/payments/" + "@CATALOG": "shared/catalog/" + "@CATALOG_INDEX": "shared/catalog/CATALOG-INDEX.yml" + "@CATALOG_AUTH": "shared/catalog/auth/" + "@CATALOG_SESSION": "shared/catalog/session-management/" + "@CATALOG_RATELIMIT": "shared/catalog/rate-limiting/" + "@CATALOG_NOTIFY": "shared/catalog/notifications/" + "@CATALOG_TENANT": "shared/catalog/multi-tenancy/" + "@CATALOG_FLAGS": "shared/catalog/feature-flags/" + "@CATALOG_WS": "shared/catalog/websocket/" + "@CATALOG_PAYMENTS": "shared/catalog/payments/" # ═══════════════════════════════════════════════════════════════════ # CAPVED - CICLO DE VIDA DE TAREAS (🆕 v2.0) @@ -222,8 +222,8 @@ niveles: # ═══════════════════════════════════════════════════════════════════ "@CORE": "core/" "@CORE_ORCH": "core/orchestration/" - "@CORE_CATALOG": "core/catalog/" - "@CORE_MODULES": "core/modules/" + "@CORE_CATALOG": "shared/catalog/" + "@CORE_MODULES": "shared/modules/" # ═══════════════════════════════════════════════════════════════════ # NIVEL 2: PROYECTOS (alias dinámicos) diff --git a/scripts/add_yaml.sh b/scripts/add_yaml.sh new file mode 100644 index 000000000..f32b6b0a5 --- /dev/null +++ b/scripts/add_yaml.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# +# Script to add YAML front-matter to markdown files +# + +DOCS_PATH="/home/isem/workspace-v1/projects/trading-platform/docs" +PROCESSED=0 +SKIPPED=0 + +add_yaml_to_file() { + local file="$1" + local first_line=$(head -1 "$file") + + # Skip if already has YAML + if [ "$first_line" = "---" ]; then + ((SKIPPED++)) + return + fi + + local filename=$(basename "$file") + local dirname=$(dirname "$file") + local parent_dir=$(basename "$dirname") + local content=$(cat "$file") + + # Extract title from first H1 + local title=$(echo "$content" | grep -m 1 "^# " | sed 's/^# //' | sed 's/:.*$//') + + # Extract epic from path + local epic=$(echo "$file" | grep -o 'OQI-[0-9]*' | head -1) + + # Determine file type and create YAML + local yaml="" + + if [[ "$filename" == RF-* ]]; then + local file_id=$(echo "$filename" | sed 's/\.md$//' | grep -o '^RF-[A-Z]*-[0-9]*') + yaml="--- +id: \"$file_id\" +title: \"$title\" +type: \"Requirement\" +status: \"Done\" +priority: \"Alta\" +epic: \"$epic\" +project: \"trading-platform\" +version: \"1.0.0\" +created_date: \"2025-12-05\" +updated_date: \"2026-01-04\" +--- + +" + elif [[ "$filename" == ET-* ]]; then + local file_id=$(echo "$filename" | sed 's/\.md$//' | grep -o '^ET-[A-Z]*-[0-9]*') + yaml="--- +id: \"$file_id\" +title: \"$title\" +type: \"Technical Specification\" +status: \"Done\" +priority: \"Alta\" +epic: \"$epic\" +project: \"trading-platform\" +version: \"1.0.0\" +created_date: \"2025-12-05\" +updated_date: \"2026-01-04\" +--- + +" + elif [[ "$filename" == US-* ]]; then + local file_id=$(echo "$filename" | sed 's/\.md$//' | grep -o '^US-[A-Z]*-[0-9]*') + yaml="--- +id: \"$file_id\" +title: \"$title\" +type: \"User Story\" +status: \"Done\" +priority: \"Media\" +epic: \"$epic\" +project: \"trading-platform\" +story_points: 3 +created_date: \"2025-12-05\" +updated_date: \"2026-01-04\" +--- + +" + elif [[ "$filename" == "_MAP.md" ]]; then + yaml="--- +id: \"MAP-$parent_dir\" +title: \"Mapa de $parent_dir\" +type: \"Index\" +project: \"trading-platform\" +updated_date: \"2026-01-04\" +--- + +" + else + local file_id=$(echo "$filename" | sed 's/\.md$//') + yaml="--- +id: \"$file_id\" +title: \"$title\" +type: \"Documentation\" +project: \"trading-platform\" +version: \"1.0.0\" +updated_date: \"2026-01-04\" +--- + +" + fi + + # Write new content + echo -n "$yaml$content" > "$file" + + ((PROCESSED++)) + echo "PROCESSED: ${file#$DOCS_PATH/}" +} + +# Process all markdown files +find "$DOCS_PATH" -name "*.md" -type f | while read file; do + add_yaml_to_file "$file" +done + +echo "" +echo "=== SUMMARY ===" +echo "Processed: $PROCESSED" +echo "Skipped: $SKIPPED" diff --git a/scripts/add_yaml_frontmatter.py b/scripts/add_yaml_frontmatter.py new file mode 100644 index 000000000..ded2dd7b5 --- /dev/null +++ b/scripts/add_yaml_frontmatter.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +""" +Script to add YAML front-matter to markdown files in trading-platform/docs +""" +import os +import re +from pathlib import Path + +docs_path = '/home/isem/workspace-v1/projects/trading-platform/docs' + +def get_epic_from_path(path): + """Extract epic code from path like OQI-001, OQI-002, etc.""" + match = re.search(r'OQI-(\d+)', path) + if match: + return f"OQI-{match.group(1)}" + return "" + +def get_title_from_content(content, filename): + """Extract title from first H1 header or filename""" + lines = content.split('\n') + for line in lines: + if line.startswith('# '): + title = line[2:].strip() + if ':' in title: + return title.split(':', 1)[1].strip() + return title + return filename.replace('.md', '').replace('-', ' ') + +def create_yaml_frontmatter(file_type, file_id, title, epic): + """Create YAML front-matter based on file type""" + if file_type == 'RF': + return f'''--- +id: "{file_id}" +title: "{title}" +type: "Requirement" +status: "Done" +priority: "Alta" +epic: "{epic}" +project: "trading-platform" +version: "1.0.0" +created_date: "2025-12-05" +updated_date: "2026-01-04" +--- + +''' + elif file_type == 'ET': + return f'''--- +id: "{file_id}" +title: "{title}" +type: "Technical Specification" +status: "Done" +priority: "Alta" +epic: "{epic}" +project: "trading-platform" +version: "1.0.0" +created_date: "2025-12-05" +updated_date: "2026-01-04" +--- + +''' + elif file_type == 'US': + return f'''--- +id: "{file_id}" +title: "{title}" +type: "User Story" +status: "Done" +priority: "Media" +epic: "{epic}" +project: "trading-platform" +story_points: 3 +created_date: "2025-12-05" +updated_date: "2026-01-04" +--- + +''' + elif file_type == 'MAP': + return f'''--- +id: "MAP-{file_id}" +title: "Mapa de {file_id}" +type: "Index" +project: "trading-platform" +updated_date: "2026-01-04" +--- + +''' + else: + return f'''--- +id: "{file_id}" +title: "{title}" +type: "Documentation" +project: "trading-platform" +version: "1.0.0" +updated_date: "2026-01-04" +--- + +''' + +def process_file(filepath): + """Add YAML front-matter to file if missing""" + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + if content.strip().startswith('---'): + return False + filename = os.path.basename(filepath) + if filename.startswith('RF-'): + file_type = 'RF' + match = re.match(r'(RF-[A-Z]+-\d+)', filename) + file_id = match.group(1) if match else filename.replace('.md', '') + elif filename.startswith('ET-'): + file_type = 'ET' + match = re.match(r'(ET-[A-Z]+-\d+)', filename) + file_id = match.group(1) if match else filename.replace('.md', '') + elif filename.startswith('US-'): + file_type = 'US' + match = re.match(r'(US-[A-Z]+-\d+)', filename) + file_id = match.group(1) if match else filename.replace('.md', '') + elif filename == '_MAP.md': + file_type = 'MAP' + file_id = os.path.basename(os.path.dirname(filepath)) + else: + file_type = 'OTHER' + file_id = filename.replace('.md', '') + epic = get_epic_from_path(filepath) + title = get_title_from_content(content, filename) + yaml_header = create_yaml_frontmatter(file_type, file_id, title, epic) + new_content = yaml_header + content + with open(filepath, 'w', encoding='utf-8') as f: + f.write(new_content) + return True + +def main(): + processed = 0 + skipped = 0 + for root, dirs, files in os.walk(docs_path): + for filename in files: + if filename.endswith('.md'): + filepath = os.path.join(root, filename) + if process_file(filepath): + processed += 1 + print(f"PROCESSED: {os.path.relpath(filepath, docs_path)}") + else: + skipped += 1 + print(f"\n=== SUMMARY ===") + print(f"Processed (added YAML): {processed}") + print(f"Skipped (already had YAML): {skipped}") + print(f"Total: {processed + skipped}") + +if __name__ == '__main__': + main() diff --git a/scripts/create-gitea-repos-api.sh b/scripts/create-gitea-repos-api.sh new file mode 100755 index 000000000..94182a972 --- /dev/null +++ b/scripts/create-gitea-repos-api.sh @@ -0,0 +1,147 @@ +#!/bin/bash +# ============================================================================= +# Script: Crear repositorios en Gitea via API +# Fecha: 2026-01-04 +# Uso: ./create-gitea-repos-api.sh +# ============================================================================= + +GITEA_URL="http://72.60.226.4:3000" +GITEA_USER="rckrdmrd" + +# Colores +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +# Verificar token +if [ -z "$1" ]; then + echo -e "${RED}Error: Se requiere token de API${NC}" + echo "" + echo "Uso: $0 " + echo "" + echo "Para obtener el token:" + echo " 1. Ir a ${GITEA_URL}/${GITEA_USER}" + echo " 2. Settings -> Applications -> Generate New Token" + echo " 3. Dar permisos de 'repo' y 'write:repository'" + exit 1 +fi + +GITEA_TOKEN="$1" + +echo -e "${GREEN}=== Creando repositorios en Gitea via API ===${NC}" +echo "" + +# Funcion para crear repositorio +create_repo() { + local repo_name=$1 + local description=$2 + + echo -ne "${YELLOW}Creando: $repo_name... ${NC}" + + # Verificar si existe + exists=$(curl -s -o /dev/null -w "%{http_code}" \ + "${GITEA_URL}/api/v1/repos/${GITEA_USER}/${repo_name}" \ + -H "Authorization: token ${GITEA_TOKEN}") + + if [ "$exists" = "200" ]; then + echo -e "${GREEN}Ya existe${NC}" + return 0 + fi + + # Crear repositorio + response=$(curl -s -X POST \ + "${GITEA_URL}/api/v1/user/repos" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"${repo_name}\", + \"description\": \"${description}\", + \"private\": false, + \"auto_init\": false + }") + + # Verificar resultado + if echo "$response" | grep -q "\"name\":\"${repo_name}\""; then + echo -e "${GREEN}Creado${NC}" + else + error=$(echo "$response" | python3 -c "import json,sys; print(json.load(sys.stdin).get('message', 'Unknown error'))" 2>/dev/null) + echo -e "${RED}Error: $error${NC}" + fi +} + +# ============================================================================= +# REPOSITORIOS PRINCIPALES DE PROYECTOS +# ============================================================================= + +echo "--- Repositorios Principales ---" +echo "" + +# ERP Suite +create_repo "erp-suite" "ERP Suite - Sistema multi-vertical" + +# ERP Core +create_repo "erp-core" "ERP Core - Modulos base compartidos" + +# ERP Verticales +create_repo "erp-construccion" "ERP para empresas de construccion" +create_repo "erp-clinicas" "ERP para clinicas medicas" +create_repo "erp-retail" "ERP para retail" +create_repo "erp-mecanicas-diesel" "ERP para mecanicas diesel" +create_repo "erp-vidrio-templado" "ERP para fabricas de vidrio templado" + +# Otros proyectos +create_repo "trading-platform" "Plataforma de trading y educacion financiera" +create_repo "betting-analytics" "Analytics para apuestas deportivas" +create_repo "inmobiliaria-analytics" "Analytics inmobiliario" +create_repo "platform-marketing-content" "Plataforma de marketing de contenido" + +echo "" +echo "--- Subrepositorios ERP Retail ---" +echo "" +create_repo "erp-retail-backend" "ERP Retail - Backend API" +create_repo "erp-retail-frontend-web" "ERP Retail - Frontend Web" +create_repo "erp-retail-database" "ERP Retail - Database Scripts" + +echo "" +echo "--- Subrepositorios Trading Platform ---" +echo "" +create_repo "trading-platform-backend" "Trading Platform - Backend API" +create_repo "trading-platform-frontend" "Trading Platform - Frontend Web" +create_repo "trading-platform-database" "Trading Platform - Database Scripts" +create_repo "trading-platform-ml-engine" "Trading Platform - ML Engine" +create_repo "trading-platform-data-service" "Trading Platform - Data Service" + +echo "" +echo "--- Subrepositorios Betting Analytics ---" +echo "" +create_repo "betting-analytics-backend" "Betting Analytics - Backend API" +create_repo "betting-analytics-frontend" "Betting Analytics - Frontend Web" +create_repo "betting-analytics-database" "Betting Analytics - Database Scripts" + +echo "" +echo "--- Subrepositorios Inmobiliaria Analytics ---" +echo "" +create_repo "inmobiliaria-analytics-backend" "Inmobiliaria Analytics - Backend API" +create_repo "inmobiliaria-analytics-frontend" "Inmobiliaria Analytics - Frontend Web" +create_repo "inmobiliaria-analytics-database" "Inmobiliaria Analytics - Database Scripts" + +echo "" +echo "--- Subrepositorios Platform Marketing Content ---" +echo "" +create_repo "platform-marketing-content-backend" "Platform Marketing Content - Backend API" +create_repo "platform-marketing-content-frontend" "Platform Marketing Content - Frontend Web" +create_repo "platform-marketing-content-database" "Platform Marketing Content - Database Scripts" + +echo "" +echo -e "${GREEN}=== Proceso completado ===${NC}" +echo "" +echo "Para hacer push de cada proyecto:" +echo " cd /home/isem/workspace-v1/projects/[PROYECTO]" +echo " git push -u origin main" +echo "" +echo "Subrepositorios configurados:" +echo " - erp-construccion: backend, frontend, database (YA PUSHED)" +echo " - erp-core: backend, frontend, database (YA PUSHED)" +echo " - erp-mecanicas-diesel: backend, frontend, database (YA PUSHED)" +echo "" diff --git a/scripts/create-gitea-repos.sh b/scripts/create-gitea-repos.sh new file mode 100755 index 000000000..c1805b8db --- /dev/null +++ b/scripts/create-gitea-repos.sh @@ -0,0 +1,111 @@ +#!/bin/bash +# ============================================================================= +# Script: Crear repositorios en Gitea para proyectos del workspace +# Fecha: 2025-01-04 +# ============================================================================= + +GITEA_URL="http://72.60.226.4:3000" +GITEA_USER="rckrdmrd" +SSH_HOST="gitea-server" + +# Colores +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +echo -e "${GREEN}=== Creando repositorios en Gitea ===${NC}" +echo "" + +# Función para crear repositorio via API (requiere token) +# Por ahora usamos git init y push +create_repo() { + local repo_name=$1 + local description=$2 + local local_path=$3 + + echo -e "${YELLOW}Creando: $repo_name${NC}" + + if [ -n "$local_path" ] && [ -d "$local_path" ]; then + cd "$local_path" + + # Verificar si ya tiene .git + if [ -d ".git" ]; then + echo " Ya tiene .git, verificando remote..." + existing_remote=$(git remote get-url origin 2>/dev/null) + if [ -n "$existing_remote" ]; then + echo " Remote existente: $existing_remote" + return 0 + fi + else + git init + fi + + # Configurar remote + git remote add origin git@${SSH_HOST}:${GITEA_USER}/${repo_name}.git 2>/dev/null || \ + git remote set-url origin git@${SSH_HOST}:${GITEA_USER}/${repo_name}.git + + echo -e " ${GREEN}Configurado: git@${SSH_HOST}:${GITEA_USER}/${repo_name}.git${NC}" + else + echo -e " ${RED}Path no existe: $local_path${NC}" + fi +} + +# ============================================================================= +# REPOSITORIOS PRINCIPALES (proyectos completos) +# ============================================================================= + +echo "--- Repositorios Principales ---" + +# erp-construccion (subrepos ya existen) +create_repo "erp-construccion" "ERP para construccion - proyecto principal" \ + "/home/isem/workspace-v1/projects/erp-construccion" + +# erp-core (subrepos ya existen) +create_repo "erp-core" "ERP Core - modulos base compartidos" \ + "/home/isem/workspace-v1/projects/erp-core" + +# erp-mecanicas-diesel (subrepos ya existen) +create_repo "erp-mecanicas-diesel" "ERP para mecanicas diesel" \ + "/home/isem/workspace-v1/projects/erp-mecanicas-diesel" + +# erp-suite +create_repo "erp-suite" "ERP Suite - multi-vertical" \ + "/home/isem/workspace-v1/projects/erp-suite" + +# erp-clinicas +create_repo "erp-clinicas" "ERP para clinicas medicas" \ + "/home/isem/workspace-v1/projects/erp-clinicas" + +# erp-retail +create_repo "erp-retail" "ERP para retail" \ + "/home/isem/workspace-v1/projects/erp-retail" + +# erp-vidrio-templado +create_repo "erp-vidrio-templado" "ERP para vidrio templado" \ + "/home/isem/workspace-v1/projects/erp-vidrio-templado" + +# trading-platform +create_repo "trading-platform" "Plataforma de trading" \ + "/home/isem/workspace-v1/projects/trading-platform" + +# betting-analytics +create_repo "betting-analytics" "Analytics para apuestas deportivas" \ + "/home/isem/workspace-v1/projects/betting-analytics" + +# inmobiliaria-analytics +create_repo "inmobiliaria-analytics" "Analytics inmobiliario" \ + "/home/isem/workspace-v1/projects/inmobiliaria-analytics" + +# platform_marketing_content +create_repo "platform-marketing-content" "Plataforma de marketing y contenido" \ + "/home/isem/workspace-v1/projects/platform_marketing_content" + +echo "" +echo -e "${GREEN}=== Repositorios principales configurados ===${NC}" +echo "" +echo "Para crear los repositorios en Gitea, ejecuta desde cada proyecto:" +echo " git push -u origin main" +echo "" +echo "O crea los repositorios vacios primero en:" +echo " ${GITEA_URL}/${GITEA_USER}" diff --git a/scripts/fix_yaml_frontmatter.py b/scripts/fix_yaml_frontmatter.py new file mode 100644 index 000000000..9e9f693dd --- /dev/null +++ b/scripts/fix_yaml_frontmatter.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +""" +Script para agregar YAML front-matter a archivos markdown que no lo tienen. +Soporta múltiples proyectos del workspace. +""" +import os +import re +from datetime import datetime +from pathlib import Path + +def extract_title_from_content(content: str, filename: str) -> str: + """Extrae título del primer H1 o usa el nombre del archivo.""" + match = re.search(r'^#\s+(.+)$', content, re.MULTILINE) + if match: + return match.group(1).strip() + # Fallback: usar nombre de archivo + name = Path(filename).stem + return name.replace('-', ' ').replace('_', ' ').title() + +def determine_file_type(filepath: str, filename: str) -> str: + """Determina el tipo de documento basado en path y nombre.""" + filepath_lower = filepath.lower() + filename_lower = filename.lower() + + # Por prefijo de archivo + if filename_lower.startswith('rf-'): + return 'Requirement' + elif filename_lower.startswith('us-'): + return 'User Story' + elif filename_lower.startswith('et-'): + return 'Technical Specification' + elif filename_lower.startswith('adr-'): + return 'ADR' + elif filename_lower.startswith('epic-'): + return 'Epic' + elif filename_lower.startswith('pmc-') or filename_lower.startswith('oqi-'): + return 'Module Definition' + + # Por nombre específico + if '_map' in filename_lower or '_index' in filename_lower: + return 'Index' + elif 'readme' in filename_lower: + return 'README' + elif 'vision' in filename_lower: + return 'Vision' + elif 'arquitectura' in filename_lower or 'architecture' in filename_lower: + return 'Architecture' + elif 'roadmap' in filename_lower: + return 'Roadmap' + elif 'guia' in filename_lower or 'guide' in filename_lower: + return 'Guide' + elif 'glosario' in filename_lower: + return 'Glossary' + elif 'board' in filename_lower: + return 'Planning' + elif 'definition-of' in filename_lower: + return 'Process' + elif 'auditoria' in filename_lower: + return 'Audit' + elif 'analisis' in filename_lower: + return 'Analysis' + elif 'modelo' in filename_lower or 'esquema' in filename_lower: + return 'Model' + elif 'api' in filename_lower: + return 'API Documentation' + + # Por carpeta + if '/requerimientos/' in filepath_lower or '/requirements/' in filepath_lower: + return 'Requirement' + elif '/historias-usuario/' in filepath_lower or '/user-stories/' in filepath_lower: + return 'User Story' + elif '/especificaciones/' in filepath_lower: + return 'Technical Specification' + elif '/adr/' in filepath_lower or '97-adr' in filepath_lower: + return 'ADR' + elif '/planning/' in filepath_lower: + return 'Planning' + elif '/guias/' in filepath_lower or '/guides/' in filepath_lower: + return 'Guide' + + return 'Documentation' + +def extract_epic_from_path(filepath: str) -> str: + """Extrae el epic/módulo del path si existe.""" + # Buscar patrones como OQI-001, PMC-001, EPIC-001, etc. + match = re.search(r'(OQI-\d+|PMC-\d+|EPIC-\d+|BA-\d+|IA-\d+)', filepath, re.IGNORECASE) + if match: + return match.group(1).upper() + return '' + +def generate_id(filename: str) -> str: + """Genera un ID único basado en el nombre del archivo.""" + name = Path(filename).stem + return name.upper().replace(' ', '-') + +def determine_status(doc_type: str) -> str: + """Determina el status por defecto según el tipo.""" + if doc_type in ['Requirement', 'User Story', 'Technical Specification']: + return 'Draft' + elif doc_type in ['Vision', 'Architecture', 'Guide']: + return 'Active' + return 'Draft' + +def has_yaml_frontmatter(content: str) -> bool: + """Verifica si el contenido ya tiene YAML front-matter.""" + return content.strip().startswith('---') + +def add_yaml_frontmatter(filepath: str, project_name: str) -> bool: + """Agrega YAML front-matter a un archivo si no lo tiene.""" + try: + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + if has_yaml_frontmatter(content): + return False + + filename = os.path.basename(filepath) + title = extract_title_from_content(content, filename) + doc_type = determine_file_type(filepath, filename) + doc_id = generate_id(filename) + epic = extract_epic_from_path(filepath) + status = determine_status(doc_type) + today = datetime.now().strftime('%Y-%m-%d') + + # Construir YAML + yaml_lines = [ + '---', + f'id: "{doc_id}"', + f'title: "{title}"', + f'type: "{doc_type}"', + ] + + if epic: + yaml_lines.append(f'epic: "{epic}"') + + yaml_lines.extend([ + f'status: "{status}"', + f'project: "{project_name}"', + f'version: "1.0.0"', + f'created_date: "{today}"', + f'updated_date: "{today}"', + '---', + '' + ]) + + yaml_header = '\n'.join(yaml_lines) + new_content = yaml_header + content + + with open(filepath, 'w', encoding='utf-8') as f: + f.write(new_content) + + return True + except Exception as e: + print(f"Error procesando {filepath}: {e}") + return False + +def process_project(docs_path: str, project_name: str): + """Procesa todos los archivos .md de un proyecto.""" + processed = 0 + skipped = 0 + errors = 0 + + for root, dirs, files in os.walk(docs_path): + for filename in files: + if filename.endswith('.md'): + filepath = os.path.join(root, filename) + try: + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + if has_yaml_frontmatter(content): + skipped += 1 + else: + if add_yaml_frontmatter(filepath, project_name): + processed += 1 + print(f"✓ {filepath}") + else: + errors += 1 + except Exception as e: + errors += 1 + print(f"✗ {filepath}: {e}") + + return processed, skipped, errors + +def main(): + base_path = '/home/isem/workspace-v1/projects' + + projects = [ + ('platform_marketing_content', 'platform_marketing_content'), + ('trading-platform', 'trading-platform'), + ] + + total_processed = 0 + total_skipped = 0 + total_errors = 0 + + for folder, project_name in projects: + docs_path = os.path.join(base_path, folder, 'docs') + if os.path.exists(docs_path): + print(f"\n=== Procesando: {project_name} ===") + processed, skipped, errors = process_project(docs_path, project_name) + print(f" Processed: {processed}, Skipped: {skipped}, Errors: {errors}") + total_processed += processed + total_skipped += skipped + total_errors += errors + else: + print(f"No existe: {docs_path}") + + print(f"\n=== TOTAL ===") + print(f"Processed: {total_processed}") + print(f"Skipped: {total_skipped}") + print(f"Errors: {total_errors}") + +if __name__ == '__main__': + main() diff --git a/scripts/push-all-projects.sh b/scripts/push-all-projects.sh new file mode 100755 index 000000000..dc2c59b67 --- /dev/null +++ b/scripts/push-all-projects.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# ============================================================================= +# Script: Push todos los proyectos a Gitea +# Fecha: 2026-01-04 +# Prerrequisito: Ejecutar create-gitea-repos-api.sh primero +# ============================================================================= + +WORKSPACE="/home/isem/workspace-v1" + +# Colores +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +echo -e "${GREEN}=== Push de proyectos a Gitea ===${NC}" +echo "" + +# Proyectos a procesar +PROJECTS=( + "erp-construccion" + "erp-core" + "erp-mecanicas-diesel" + "erp-suite" + "erp-clinicas" + "erp-retail" + "erp-vidrio-templado" + "trading-platform" + "betting-analytics" + "inmobiliaria-analytics" + "platform_marketing_content" +) + +for proj in "${PROJECTS[@]}"; do + echo -ne "${YELLOW}Pushing: $proj... ${NC}" + + proj_path="${WORKSPACE}/projects/${proj}" + + if [ ! -d "$proj_path/.git" ]; then + echo -e "${RED}Sin .git${NC}" + continue + fi + + cd "$proj_path" + + # Intentar push + result=$(git push -u origin main 2>&1) + + if [ $? -eq 0 ]; then + echo -e "${GREEN}OK${NC}" + else + if echo "$result" | grep -q "Everything up-to-date"; then + echo -e "${GREEN}Ya sincronizado${NC}" + elif echo "$result" | grep -q "repository not found\|does not exist"; then + echo -e "${RED}Repo no existe en Gitea${NC}" + else + echo -e "${RED}Error${NC}" + echo " $result" | head -2 + fi + fi +done + +echo "" +echo -e "${GREEN}=== Proceso completado ===${NC}" diff --git a/shared/README.md b/shared/README.md new file mode 100644 index 000000000..f04db39b2 --- /dev/null +++ b/shared/README.md @@ -0,0 +1,177 @@ +# Shared - Recursos Compartidos + +**Version:** 2.0.0 +**Actualizado:** 2026-01-04 + +## Descripcion + +El directorio `shared/` contiene todos los **recursos compartidos** del workspace: codigo reutilizable, catalogo de funcionalidades, tipos, constantes, y base de conocimiento con documentacion. + +## Estructura + +``` +shared/ +├── README.md # Este archivo +│ +├── catalog/ # Catalogo de funcionalidades reutilizables +│ ├── auth/ # Autenticacion JWT + Passport +│ ├── session-management/ # Gestion de sesiones +│ ├── rate-limiting/ # Limitacion de tasa +│ ├── notifications/ # Sistema de notificaciones +│ ├── multi-tenancy/ # Soporte multi-tenant con RLS +│ ├── feature-flags/ # Feature flags dinamicos +│ ├── websocket/ # Comunicacion WebSocket +│ ├── payments/ # Integracion con Stripe +│ ├── audit-logs/ # Logs de auditoria +│ ├── portales/ # Portales multi-tenant +│ ├── template-saas/ # Template SaaS completo +│ ├── CATALOG-INDEX.yml # Indice maestro +│ └── README.md +│ +├── modules/ # Codigo ejecutable compartido +│ ├── utils/ # Utilidades (date, string, validation) +│ ├── auth/ # Modulo de autenticacion +│ ├── billing/ # Modulo de facturacion +│ ├── notifications/ # Modulo de notificaciones +│ ├── payments/ # Modulo de pagos +│ ├── multitenant/ # Modulo multi-tenancy +│ └── package.json +│ +├── constants/ # Constantes globales TypeScript +│ ├── enums.constants.ts # Enums universales +│ ├── regex.constants.ts # Patrones regex +│ └── index.ts +│ +├── types/ # Tipos TypeScript compartidos +│ ├── api.types.ts # Tipos de API +│ ├── common.types.ts # Tipos comunes +│ └── index.ts +│ +└── knowledge-base/ # Base de conocimiento + ├── modules/ # Documentacion de modulos + ├── platforms/ # Plataformas base (SaaS, ERP, etc.) + ├── projects/ # Proyectos de referencia + ├── standards/ # Estandares tecnicos + ├── architecture/ # Patrones arquitectonicos + ├── patterns/ # Patrones de desarrollo + ├── propagacion/ # Sistema de propagacion + ├── templates/ # Templates + ├── reference/ # Referencias legacy + ├── CATALOGO-MODULOS.yml # Catalogo de 37 modulos + ├── TRAZABILIDAD-PROYECTOS.yml + └── README.md +``` + +## Uso + +### Catalogo de Funcionalidades + +```bash +# Buscar funcionalidad disponible +cat shared/catalog/CATALOG-INDEX.yml + +# Consultar implementacion especifica +cat shared/catalog/auth/README.md +cat shared/catalog/auth/IMPLEMENTATION.md +``` + +### Importar Modulos + +```typescript +// Utilidades +import { formatDate, slugify, isEmail } from '@shared/modules/utils'; + +// Constantes +import { UserStatus, PaymentStatus } from '@shared/constants'; +import { EMAIL_REGEX, UUID_REGEX } from '@shared/constants/regex.constants'; + +// Tipos +import { ApiResponse, PaginatedResponse } from '@shared/types'; +import { BaseEntity, Address } from '@shared/types/common.types'; +``` + +### Knowledge Base + +```bash +# Ver catalogo de modulos +cat shared/knowledge-base/CATALOGO-MODULOS.yml + +# Ver trazabilidad de proyectos +cat shared/knowledge-base/TRAZABILIDAD-PROYECTOS.yml + +# Consultar documentacion de modulo +cat shared/knowledge-base/modules/authentication/_INDEX.md +``` + +## Aliases + +```yaml +# Catalogo +@CATALOG: shared/catalog/ +@CATALOG_INDEX: shared/catalog/CATALOG-INDEX.yml +@CATALOG_AUTH: shared/catalog/auth/ +@CATALOG_SESSION: shared/catalog/session-management/ +@CATALOG_RATELIMIT: shared/catalog/rate-limiting/ +@CATALOG_NOTIFY: shared/catalog/notifications/ +@CATALOG_TENANT: shared/catalog/multi-tenancy/ +@CATALOG_FLAGS: shared/catalog/feature-flags/ +@CATALOG_WS: shared/catalog/websocket/ +@CATALOG_PAYMENTS: shared/catalog/payments/ + +# Modulos +@MODULES: shared/modules/ +@MOD_UTILS: shared/modules/utils/ +@MOD_AUTH: shared/modules/auth/ +@MOD_NOTIFY: shared/modules/notifications/ +@MOD_PAYMENTS: shared/modules/payments/ +@MOD_BILLING: shared/modules/billing/ +@MOD_TENANT: shared/modules/multitenant/ + +# Tipos y Constantes +@CONSTANTS: shared/constants/ +@TYPES: shared/types/ + +# Knowledge Base +@KB: shared/knowledge-base/ +@KB_MODULES: shared/knowledge-base/modules/ +@KB_PLATFORMS: shared/knowledge-base/platforms/ +@KB_PROJECTS: shared/knowledge-base/projects/ +``` + +## Funcionalidades del Catalogo + +| Funcionalidad | Estado | Origen | Stack | +|---------------|--------|--------|-------| +| auth | Production-Ready | Gamilit | NestJS + JWT + Passport | +| session-management | Production-Ready | Gamilit | NestJS + TypeORM | +| rate-limiting | Production-Ready | Gamilit | NestJS + @nestjs/throttler | +| notifications | Production-Ready | Gamilit | NestJS + Email/Push | +| multi-tenancy | Production-Ready | Gamilit | NestJS + PostgreSQL RLS | +| feature-flags | Production-Ready | Gamilit | NestJS + TypeORM | +| websocket | Production-Ready | Trading | NestJS + Socket.io | +| payments | Production-Ready | Trading | NestJS + Stripe | +| audit-logs | Documentando | ERP | NestJS + TypeORM | +| portales | Documentando | PMC | NestJS + Multi-tenant | +| template-saas | Documentando | Workspace | Full-stack | + +## Relacion con Proyectos + +Los proyectos en `projects/` pueden importar recursos de `shared/`: + +``` +projects/gamilit/ → usa shared/catalog/auth, shared/modules/utils +projects/erp-core/ → usa shared/catalog/multi-tenancy +projects/trading-platform/ → usa shared/catalog/websocket, payments +``` + +## Ver Tambien + +- [Catalogo de Funcionalidades](catalog/README.md) +- [Knowledge Base](knowledge-base/README.md) +- [Core (Arquitectura)](../core/README.md) +- [Control Plane (Governance)](../control-plane/README.md) + +--- + +**Mantenido por:** Architecture-Analyst +**Ultima actualizacion:** 2026-01-04 diff --git a/core/catalog/CATALOG-INDEX.yml b/shared/catalog/CATALOG-INDEX.yml similarity index 96% rename from core/catalog/CATALOG-INDEX.yml rename to shared/catalog/CATALOG-INDEX.yml index 411acc1c2..49395ef26 100644 --- a/core/catalog/CATALOG-INDEX.yml +++ b/shared/catalog/CATALOG-INDEX.yml @@ -28,7 +28,7 @@ funcionalidades: auth: nombre: "Autenticación y Autorización" - path: "core/catalog/auth/" + path: "shared/catalog/auth/" alias: "@CATALOG_AUTH" estado: "production-ready" # production-ready | documentando | pendiente origen: "projects/gamilit" @@ -68,7 +68,7 @@ funcionalidades: session-management: nombre: "Gestión de Sesiones" - path: "core/catalog/session-management/" + path: "shared/catalog/session-management/" alias: "@CATALOG_SESSION" estado: "production-ready" origen: "projects/gamilit" @@ -98,7 +98,7 @@ funcionalidades: rate-limiting: nombre: "Limitación de Tasa (Rate Limiting)" - path: "core/catalog/rate-limiting/" + path: "shared/catalog/rate-limiting/" alias: "@CATALOG_RATELIMIT" estado: "production-ready" origen: "projects/gamilit" @@ -129,7 +129,7 @@ funcionalidades: notifications: nombre: "Sistema de Notificaciones" - path: "core/catalog/notifications/" + path: "shared/catalog/notifications/" alias: "@CATALOG_NOTIFY" estado: "production-ready" origen: "projects/gamilit" @@ -165,7 +165,7 @@ funcionalidades: websocket: nombre: "Comunicación WebSocket" - path: "core/catalog/websocket/" + path: "shared/catalog/websocket/" alias: "@CATALOG_WS" estado: "production-ready" origen: "projects/trading-platform" @@ -199,7 +199,7 @@ funcionalidades: multi-tenancy: nombre: "Soporte Multi-Tenant" - path: "core/catalog/multi-tenancy/" + path: "shared/catalog/multi-tenancy/" alias: "@CATALOG_TENANT" estado: "production-ready" origen: "projects/gamilit" @@ -233,7 +233,7 @@ funcionalidades: feature-flags: nombre: "Feature Flags Dinámicos" - path: "core/catalog/feature-flags/" + path: "shared/catalog/feature-flags/" alias: "@CATALOG_FLAGS" estado: "production-ready" origen: "projects/gamilit" @@ -268,7 +268,7 @@ funcionalidades: payments: nombre: "Integración de Pagos" - path: "core/catalog/payments/" + path: "shared/catalog/payments/" alias: "@CATALOG_PAYMENTS" estado: "production-ready" origen: "projects/trading-platform" @@ -304,7 +304,7 @@ funcionalidades: template-saas: nombre: "Template SaaS Multi-tenant" - path: "core/catalog/template-saas/" + path: "shared/catalog/template-saas/" alias: "@CATALOG_SAAS" estado: "production-ready" origen: "projects/erp-core, projects/gamilit" @@ -342,7 +342,7 @@ funcionalidades: portales: nombre: "Portales y Dashboards" - path: "core/catalog/portales/" + path: "shared/catalog/portales/" alias: "@CATALOG_PORTALES" estado: "production-ready" origen: "projects/gamilit, projects/trading-platform" @@ -379,7 +379,7 @@ funcionalidades: audit-logs: nombre: "Sistema de Auditoria" - path: "core/catalog/audit-logs/" + path: "shared/catalog/audit-logs/" alias: "@CATALOG_AUDIT" estado: "production-ready" origen: "projects/gamilit, projects/erp-core" @@ -419,7 +419,7 @@ funcionalidades: # ───────────────────────────────────────────────────────────────────────────────── # # Para buscar funcionalidad por keyword: -# grep -i "{keyword}" core/catalog/CATALOG-INDEX.yml +# grep -i "{keyword}" shared/catalog/CATALOG-INDEX.yml # # Ejemplos: # grep -i "login" → auth @@ -469,7 +469,7 @@ instrucciones: - Considerar agregar al catálogo después que_hacer_si_encuentra: | - 1. Ir a: core/catalog/{funcionalidad}/ + 1. Ir a: shared/catalog/{funcionalidad}/ 2. Leer: README.md (descripción y trade-offs) 3. Seguir: IMPLEMENTATION.md (pasos) 4. Copiar: _reference/ (código base) diff --git a/core/catalog/CATALOG-USAGE-TRACKING.yml b/shared/catalog/CATALOG-USAGE-TRACKING.yml similarity index 100% rename from core/catalog/CATALOG-USAGE-TRACKING.yml rename to shared/catalog/CATALOG-USAGE-TRACKING.yml diff --git a/core/catalog/README.md b/shared/catalog/README.md similarity index 95% rename from core/catalog/README.md rename to shared/catalog/README.md index 2f193e74b..80b0a5d6b 100644 --- a/core/catalog/README.md +++ b/shared/catalog/README.md @@ -30,7 +30,7 @@ Este catálogo centraliza **código funcional probado y documentado** que puede ## ESTRUCTURA DEL CATÁLOGO ``` -core/catalog/ +shared/catalog/ ├── README.md ← ESTÁS AQUÍ ├── CATALOG-INDEX.yml # Índice máquina-readable │ @@ -103,16 +103,16 @@ core/catalog/ ### Alias Disponibles ```yaml -@CATALOG: core/catalog/ -@CATALOG_INDEX: core/catalog/CATALOG-INDEX.yml -@CATALOG_AUTH: core/catalog/auth/ -@CATALOG_SESSION: core/catalog/session-management/ -@CATALOG_RATELIMIT: core/catalog/rate-limiting/ -@CATALOG_NOTIFY: core/catalog/notifications/ -@CATALOG_TENANT: core/catalog/multi-tenancy/ -@CATALOG_FLAGS: core/catalog/feature-flags/ -@CATALOG_WS: core/catalog/websocket/ -@CATALOG_PAYMENTS: core/catalog/payments/ +@CATALOG: shared/catalog/ +@CATALOG_INDEX: shared/catalog/CATALOG-INDEX.yml +@CATALOG_AUTH: shared/catalog/auth/ +@CATALOG_SESSION: shared/catalog/session-management/ +@CATALOG_RATELIMIT: shared/catalog/rate-limiting/ +@CATALOG_NOTIFY: shared/catalog/notifications/ +@CATALOG_TENANT: shared/catalog/multi-tenancy/ +@CATALOG_FLAGS: shared/catalog/feature-flags/ +@CATALOG_WS: shared/catalog/websocket/ +@CATALOG_PAYMENTS: shared/catalog/payments/ ``` --- @@ -251,7 +251,7 @@ Una funcionalidad DEBE agregarse al catálogo si cumple **TODOS** estos criterio ```markdown PASO 1: PREPARAR ESTRUCTURA -[ ] Crear directorio: core/catalog/{nombre-en-kebab-case}/ +[ ] Crear directorio: shared/catalog/{nombre-en-kebab-case}/ [ ] Crear README.md vacío [ ] Crear IMPLEMENTATION.md vacío @@ -286,7 +286,7 @@ PASO 4: ACTUALIZAR ÍNDICES (OBLIGATORIO) PASO 5: ACTUALIZAR ALIASES (OBLIGATORIO) [ ] Editar core/orchestration/referencias/ALIASES.yml: - Agregar alias @CATALOG_{NOMBRE} - - Ejemplo: @CATALOG_AUDIT: "core/catalog/audit-logs/" + - Ejemplo: @CATALOG_AUDIT: "shared/catalog/audit-logs/" PASO 6: VALIDAR [ ] Verificar que grep encuentra la funcionalidad en CATALOG-INDEX.yml diff --git a/core/catalog/audit-logs/IMPLEMENTATION.md b/shared/catalog/audit-logs/IMPLEMENTATION.md similarity index 100% rename from core/catalog/audit-logs/IMPLEMENTATION.md rename to shared/catalog/audit-logs/IMPLEMENTATION.md diff --git a/core/catalog/audit-logs/README.md b/shared/catalog/audit-logs/README.md similarity index 100% rename from core/catalog/audit-logs/README.md rename to shared/catalog/audit-logs/README.md diff --git a/core/catalog/auth/IMPLEMENTATION.md b/shared/catalog/auth/IMPLEMENTATION.md similarity index 100% rename from core/catalog/auth/IMPLEMENTATION.md rename to shared/catalog/auth/IMPLEMENTATION.md diff --git a/core/catalog/auth/README.md b/shared/catalog/auth/README.md similarity index 100% rename from core/catalog/auth/README.md rename to shared/catalog/auth/README.md diff --git a/core/catalog/auth/_reference/README.md b/shared/catalog/auth/_reference/README.md similarity index 100% rename from core/catalog/auth/_reference/README.md rename to shared/catalog/auth/_reference/README.md diff --git a/core/catalog/auth/_reference/auth.service.reference.ts b/shared/catalog/auth/_reference/auth.service.reference.ts similarity index 100% rename from core/catalog/auth/_reference/auth.service.reference.ts rename to shared/catalog/auth/_reference/auth.service.reference.ts diff --git a/core/catalog/auth/_reference/jwt-auth.guard.reference.ts b/shared/catalog/auth/_reference/jwt-auth.guard.reference.ts similarity index 100% rename from core/catalog/auth/_reference/jwt-auth.guard.reference.ts rename to shared/catalog/auth/_reference/jwt-auth.guard.reference.ts diff --git a/core/catalog/auth/_reference/jwt.strategy.reference.ts b/shared/catalog/auth/_reference/jwt.strategy.reference.ts similarity index 100% rename from core/catalog/auth/_reference/jwt.strategy.reference.ts rename to shared/catalog/auth/_reference/jwt.strategy.reference.ts diff --git a/core/catalog/auth/_reference/roles.guard.reference.ts b/shared/catalog/auth/_reference/roles.guard.reference.ts similarity index 100% rename from core/catalog/auth/_reference/roles.guard.reference.ts rename to shared/catalog/auth/_reference/roles.guard.reference.ts diff --git a/core/catalog/auth/_reference_frontend/auth-hooks.example.ts b/shared/catalog/auth/_reference_frontend/auth-hooks.example.ts similarity index 100% rename from core/catalog/auth/_reference_frontend/auth-hooks.example.ts rename to shared/catalog/auth/_reference_frontend/auth-hooks.example.ts diff --git a/core/catalog/feature-flags/IMPLEMENTATION.md b/shared/catalog/feature-flags/IMPLEMENTATION.md similarity index 100% rename from core/catalog/feature-flags/IMPLEMENTATION.md rename to shared/catalog/feature-flags/IMPLEMENTATION.md diff --git a/core/catalog/feature-flags/README.md b/shared/catalog/feature-flags/README.md similarity index 100% rename from core/catalog/feature-flags/README.md rename to shared/catalog/feature-flags/README.md diff --git a/core/catalog/feature-flags/_reference/feature-flags.service.reference.ts b/shared/catalog/feature-flags/_reference/feature-flags.service.reference.ts similarity index 100% rename from core/catalog/feature-flags/_reference/feature-flags.service.reference.ts rename to shared/catalog/feature-flags/_reference/feature-flags.service.reference.ts diff --git a/core/catalog/multi-tenancy/IMPLEMENTATION.md b/shared/catalog/multi-tenancy/IMPLEMENTATION.md similarity index 100% rename from core/catalog/multi-tenancy/IMPLEMENTATION.md rename to shared/catalog/multi-tenancy/IMPLEMENTATION.md diff --git a/core/catalog/multi-tenancy/README.md b/shared/catalog/multi-tenancy/README.md similarity index 100% rename from core/catalog/multi-tenancy/README.md rename to shared/catalog/multi-tenancy/README.md diff --git a/core/catalog/multi-tenancy/_reference/tenant.guard.reference.ts b/shared/catalog/multi-tenancy/_reference/tenant.guard.reference.ts similarity index 100% rename from core/catalog/multi-tenancy/_reference/tenant.guard.reference.ts rename to shared/catalog/multi-tenancy/_reference/tenant.guard.reference.ts diff --git a/core/catalog/notifications/IMPLEMENTATION.md b/shared/catalog/notifications/IMPLEMENTATION.md similarity index 100% rename from core/catalog/notifications/IMPLEMENTATION.md rename to shared/catalog/notifications/IMPLEMENTATION.md diff --git a/core/catalog/notifications/README.md b/shared/catalog/notifications/README.md similarity index 100% rename from core/catalog/notifications/README.md rename to shared/catalog/notifications/README.md diff --git a/core/catalog/notifications/_reference/notification.service.reference.ts b/shared/catalog/notifications/_reference/notification.service.reference.ts similarity index 100% rename from core/catalog/notifications/_reference/notification.service.reference.ts rename to shared/catalog/notifications/_reference/notification.service.reference.ts diff --git a/core/catalog/payments/IMPLEMENTATION.md b/shared/catalog/payments/IMPLEMENTATION.md similarity index 100% rename from core/catalog/payments/IMPLEMENTATION.md rename to shared/catalog/payments/IMPLEMENTATION.md diff --git a/core/catalog/payments/README.md b/shared/catalog/payments/README.md similarity index 100% rename from core/catalog/payments/README.md rename to shared/catalog/payments/README.md diff --git a/core/catalog/payments/_reference/README.md b/shared/catalog/payments/_reference/README.md similarity index 100% rename from core/catalog/payments/_reference/README.md rename to shared/catalog/payments/_reference/README.md diff --git a/core/catalog/payments/_reference/payment.service.reference.ts b/shared/catalog/payments/_reference/payment.service.reference.ts similarity index 100% rename from core/catalog/payments/_reference/payment.service.reference.ts rename to shared/catalog/payments/_reference/payment.service.reference.ts diff --git a/core/catalog/portales/IMPLEMENTATION.md b/shared/catalog/portales/IMPLEMENTATION.md similarity index 100% rename from core/catalog/portales/IMPLEMENTATION.md rename to shared/catalog/portales/IMPLEMENTATION.md diff --git a/core/catalog/portales/README.md b/shared/catalog/portales/README.md similarity index 100% rename from core/catalog/portales/README.md rename to shared/catalog/portales/README.md diff --git a/core/catalog/rate-limiting/IMPLEMENTATION.md b/shared/catalog/rate-limiting/IMPLEMENTATION.md similarity index 100% rename from core/catalog/rate-limiting/IMPLEMENTATION.md rename to shared/catalog/rate-limiting/IMPLEMENTATION.md diff --git a/core/catalog/rate-limiting/README.md b/shared/catalog/rate-limiting/README.md similarity index 100% rename from core/catalog/rate-limiting/README.md rename to shared/catalog/rate-limiting/README.md diff --git a/core/catalog/rate-limiting/_reference/rate-limiter.service.reference.ts b/shared/catalog/rate-limiting/_reference/rate-limiter.service.reference.ts similarity index 100% rename from core/catalog/rate-limiting/_reference/rate-limiter.service.reference.ts rename to shared/catalog/rate-limiting/_reference/rate-limiter.service.reference.ts diff --git a/core/catalog/session-management/IMPLEMENTATION.md b/shared/catalog/session-management/IMPLEMENTATION.md similarity index 100% rename from core/catalog/session-management/IMPLEMENTATION.md rename to shared/catalog/session-management/IMPLEMENTATION.md diff --git a/core/catalog/session-management/README.md b/shared/catalog/session-management/README.md similarity index 100% rename from core/catalog/session-management/README.md rename to shared/catalog/session-management/README.md diff --git a/core/catalog/session-management/_reference/session-management.service.reference.ts b/shared/catalog/session-management/_reference/session-management.service.reference.ts similarity index 100% rename from core/catalog/session-management/_reference/session-management.service.reference.ts rename to shared/catalog/session-management/_reference/session-management.service.reference.ts diff --git a/core/catalog/template-saas/IMPLEMENTATION.md b/shared/catalog/template-saas/IMPLEMENTATION.md similarity index 100% rename from core/catalog/template-saas/IMPLEMENTATION.md rename to shared/catalog/template-saas/IMPLEMENTATION.md diff --git a/core/catalog/template-saas/README.md b/shared/catalog/template-saas/README.md similarity index 100% rename from core/catalog/template-saas/README.md rename to shared/catalog/template-saas/README.md diff --git a/core/catalog/websocket/IMPLEMENTATION.md b/shared/catalog/websocket/IMPLEMENTATION.md similarity index 100% rename from core/catalog/websocket/IMPLEMENTATION.md rename to shared/catalog/websocket/IMPLEMENTATION.md diff --git a/core/catalog/websocket/README.md b/shared/catalog/websocket/README.md similarity index 100% rename from core/catalog/websocket/README.md rename to shared/catalog/websocket/README.md diff --git a/core/catalog/websocket/_reference/websocket.gateway.reference.ts b/shared/catalog/websocket/_reference/websocket.gateway.reference.ts similarity index 100% rename from core/catalog/websocket/_reference/websocket.gateway.reference.ts rename to shared/catalog/websocket/_reference/websocket.gateway.reference.ts diff --git a/core/constants/enums.constants.ts b/shared/constants/enums.constants.ts similarity index 99% rename from core/constants/enums.constants.ts rename to shared/constants/enums.constants.ts index ab5d20225..45c06a9a0 100644 --- a/core/constants/enums.constants.ts +++ b/shared/constants/enums.constants.ts @@ -5,7 +5,7 @@ * Estos son los enums "universales" que aplican a múltiples proyectos. * Los enums específicos de cada proyecto deben estar en su propio repositorio. * - * @module @core/constants/enums + * @module @shared/constants/enums * @version 1.0.0 */ diff --git a/core/constants/index.ts b/shared/constants/index.ts similarity index 87% rename from core/constants/index.ts rename to shared/constants/index.ts index c436a6acd..de1acdf70 100644 --- a/core/constants/index.ts +++ b/shared/constants/index.ts @@ -3,7 +3,7 @@ * * Universal constants shared across all projects in the workspace. * - * @module @core/constants + * @module @shared/constants * @version 1.0.0 */ diff --git a/core/constants/regex.constants.ts b/shared/constants/regex.constants.ts similarity index 99% rename from core/constants/regex.constants.ts rename to shared/constants/regex.constants.ts index a999d93bc..48a1219c3 100644 --- a/core/constants/regex.constants.ts +++ b/shared/constants/regex.constants.ts @@ -3,7 +3,7 @@ * * Regex patterns compartidos entre todos los proyectos del workspace. * - * @module @core/constants/regex + * @module @shared/constants/regex * @version 1.0.0 */ diff --git a/shared/knowledge-base/README.md b/shared/knowledge-base/README.md index 4b1fa791d..24d1eecaa 100644 --- a/shared/knowledge-base/README.md +++ b/shared/knowledge-base/README.md @@ -138,14 +138,14 @@ El Knowledge Base es parte del ecosistema NEXUS: ``` workspace-v1/ ├── orchestration/ # Sistema de directivas SIMCO -├── core/catalog/ # Codigo de catalogo (legacy) +├── shared/catalog/ # Codigo de catalogo (legacy) ├── shared/knowledge-base/ # ESTE DIRECTORIO └── projects/ # Proyectos de produccion ``` -### Relacion con core/catalog +### Relacion con shared/catalog -- **core/catalog/**: Codigo legacy, referencia +- **shared/catalog/**: Codigo legacy, referencia - **shared/knowledge-base/modules/**: Modulos documentados y versionados --- diff --git a/shared/knowledge-base/TRAZABILIDAD-PROPAGACION.yml b/shared/knowledge-base/TRAZABILIDAD-PROPAGACION.yml new file mode 100644 index 000000000..d119ebc9a --- /dev/null +++ b/shared/knowledge-base/TRAZABILIDAD-PROPAGACION.yml @@ -0,0 +1,139 @@ +# TRAZABILIDAD DE PROPAGACIÓN +# Sistema: SIMCO - NEXUS v4.0 +# Propósito: Tracking de propagaciones entre proyectos y KB +# Versión: 1.0.0 +# Fecha: 2026-01-04 + +# ═══════════════════════════════════════════════════════════════════════════════ +# REGISTRO DE PROPAGACIONES +# ═══════════════════════════════════════════════════════════════════════════════ + +propagaciones: + # Ejemplo de entrada: + # - id: "PROP-2026-01-001" + # fecha: "2026-01-04" + # tipo: "bug_fix" + # origen: + # proyecto: "gamilit" + # archivo: "src/utils/validation.ts" + # tarea: "HU-123" + # descripcion: "Corrección de validación de emails" + # destinos: + # - proyecto: "erp-core" + # estado: "completado" + # fecha_aplicado: "2026-01-04" + # - proyecto: "knowledge-base" + # ubicacion: "patterns/validation/" + # estado: "completado" + # estado_general: "completado" + # notas: "" + + # Agregar nuevas propagaciones aquí: + - id: "PROP-TEMPLATE" + fecha: "{YYYY-MM-DD}" + tipo: "{security_fix | bug_fix | feature | refactor | docs}" + + origen: + proyecto: "{nombre_proyecto}" + archivo: "{ruta/archivo}" + tarea: "{HU-XXX}" + descripcion_cambio: "{qué se cambió}" + + destinos: + - proyecto: "{nombre_destino}" + tipo_destino: "{proyecto | kb | catalog}" + ubicacion: "{ruta si aplica}" + estado: "{pendiente | en_progreso | completado | fallido}" + fecha_aplicado: "" + verificado: false + notas: "" + + estado_general: "{pendiente | en_progreso | completado | parcial | fallido}" + + migration_guide: + requerido: false + ubicacion: "" + + verificacion: + tests_pasados: false + build_pasado: false + revisado_por: "" + + notas: "" + +# ═══════════════════════════════════════════════════════════════════════════════ +# ÍNDICE POR PROYECTO ORIGEN +# ═══════════════════════════════════════════════════════════════════════════════ + +indice_por_origen: + gamilit: [] + trading-platform: [] + betting-analytics: [] + inmobiliaria-analytics: [] + platform_marketing_content: [] + erp-suite: [] + erp-core: [] + erp-clinicas: [] + erp-construccion: [] + erp-mecanicas-diesel: [] + erp-retail: [] + erp-vidrio-templado: [] + +# ═══════════════════════════════════════════════════════════════════════════════ +# ÍNDICE POR DESTINO +# ═══════════════════════════════════════════════════════════════════════════════ + +indice_por_destino: + knowledge-base: + patterns: [] + lessons-learned: [] + reference: [] + antipatterns: [] + + catalog: + components: [] + utils: [] + hooks: [] + services: [] + + proyectos: + gamilit: [] + trading-platform: [] + erp-core: [] + # ... otros proyectos + +# ═══════════════════════════════════════════════════════════════════════════════ +# ESTADÍSTICAS +# ═══════════════════════════════════════════════════════════════════════════════ + +estadisticas: + total_propagaciones: 0 + por_tipo: + security_fix: 0 + bug_fix: 0 + feature: 0 + refactor: 0 + docs: 0 + + por_estado: + completadas: 0 + en_progreso: 0 + pendientes: 0 + fallidas: 0 + + sla: + security_cumplido: 0 + security_incumplido: 0 + bug_cumplido: 0 + bug_incumplido: 0 + + ultima_actualizacion: "2026-01-04" + +# ═══════════════════════════════════════════════════════════════════════════════ +# ALERTAS PENDIENTES +# ═══════════════════════════════════════════════════════════════════════════════ + +alertas: + security_pendientes: [] # IDs de propagaciones security pendientes + sla_en_riesgo: [] # IDs próximos a vencer SLA + fallidas_sin_resolver: [] # IDs fallidos sin resolución diff --git a/shared/knowledge-base/architecture/PATRON-MULTI-TENANT.md b/shared/knowledge-base/architecture/PATRON-MULTI-TENANT.md index fd390789b..302fd06a6 100644 --- a/shared/knowledge-base/architecture/PATRON-MULTI-TENANT.md +++ b/shared/knowledge-base/architecture/PATRON-MULTI-TENANT.md @@ -187,7 +187,7 @@ describe('Tenant Isolation', () => { ## Referencias -- `core/catalog/multi-tenancy/` - Modulo del catalogo +- `shared/catalog/multi-tenancy/` - Modulo del catalogo - `projects/erp-core/database/ddl/` - Ejemplos de DDL - PostgreSQL RLS Documentation diff --git a/shared/knowledge-base/lessons-learned/LESSONS-WORKSPACE-V1.md b/shared/knowledge-base/lessons-learned/LESSONS-WORKSPACE-V1.md index a79b7afbc..a4c9b59a9 100644 --- a/shared/knowledge-base/lessons-learned/LESSONS-WORKSPACE-V1.md +++ b/shared/knowledge-base/lessons-learned/LESSONS-WORKSPACE-V1.md @@ -76,7 +76,7 @@ Migracion del workspace original a workspace-v1 con nueva estructura de: **Problema:** Codigo duplicado entre proyectos. -**Solucion:** Expandir `core/catalog/` con: +**Solucion:** Expandir `shared/catalog/` con: - README.md (descripcion, trade-offs) - IMPLEMENTATION.md (guia paso a paso) - _reference/ (codigo ejemplo) diff --git a/shared/knowledge-base/patterns/PATRON-RLS-POLICIES.md b/shared/knowledge-base/patterns/PATRON-RLS-POLICIES.md index ac4b87aba..8d9503116 100644 --- a/shared/knowledge-base/patterns/PATRON-RLS-POLICIES.md +++ b/shared/knowledge-base/patterns/PATRON-RLS-POLICIES.md @@ -234,7 +234,7 @@ ORDER BY schemaname, tablename; ## Referencias - `architecture/PATRON-MULTI-TENANT.md` -- `core/catalog/multi-tenancy/` +- `shared/catalog/multi-tenancy/` - PostgreSQL RLS Documentation --- diff --git a/shared/knowledge-base/projects/erp-core/docs/00-vision-general/VISION-ERP-CORE.md b/shared/knowledge-base/projects/erp-core/docs/00-vision-general/VISION-ERP-CORE.md index 2ed20593f..654453c05 100644 --- a/shared/knowledge-base/projects/erp-core/docs/00-vision-general/VISION-ERP-CORE.md +++ b/shared/knowledge-base/projects/erp-core/docs/00-vision-general/VISION-ERP-CORE.md @@ -4,6 +4,8 @@ ERP Core es la **base generica reutilizable** que proporciona el 60-70% del codigo compartido para todas las verticales del ERP Suite. Es una adaptacion de los patrones de Odoo al stack TypeScript/Node.js/React. +**Modelo de Negocio:** Plataforma SaaS multi-tenant con facturacion per-seat, integraciones de pago fisico y digital, chatbot IA conversacional, y aplicaciones moviles por perfil de trabajador. + --- ## Proposito @@ -18,6 +20,10 @@ Desarrollar ERPs verticales desde cero es costoso y repetitivo. El 60-70% de la - Productos e inventario - Ventas y compras - Contabilidad basica +- **Plataforma SaaS con suscripciones** +- **Procesamiento de pagos (online y terminales)** +- **Asistente IA conversacional** +- **Apps moviles con control de asistencia** ### Solucion @@ -26,6 +32,9 @@ ERP Core provee esta funcionalidad comun de forma: - **Extensible:** Las verticales pueden extender sin modificar - **Multi-tenant:** Aislamiento por tenant desde el diseno - **Documentado:** Documentacion antes de desarrollo +- **SaaS-Ready:** Billing, suscripciones y feature flags incluidos +- **IA-First:** Chatbot inteligente con MCP Server y RAG +- **Mobile-First:** Apps nativas por perfil con biometricos --- @@ -92,23 +101,49 @@ ERP Core provee esta funcionalidad comun de forma: ## Modulos Core (MGN-*) -| Codigo | Modulo | Descripcion | Prioridad | Estado | -|--------|--------|-------------|-----------|--------| -| MGN-001 | auth | Autenticacion JWT, OAuth, sessions | P0 | En desarrollo | -| MGN-002 | users | Gestion de usuarios CRUD | P0 | En desarrollo | -| MGN-003 | roles | Roles y permisos (RBAC) | P0 | Planificado | -| MGN-004 | tenants | Multi-tenancy, aislamiento | P0 | Planificado | -| MGN-005 | catalogs | Catalogos maestros genericos | P1 | Planificado | -| MGN-006 | settings | Configuracion del sistema | P1 | Planificado | -| MGN-007 | audit | Auditoria y logs | P1 | Planificado | -| MGN-008 | notifications | Sistema de notificaciones | P2 | Planificado | -| MGN-009 | reports | Reportes genericos | P2 | Planificado | -| MGN-010 | financial | Contabilidad basica | P1 | Planificado | -| MGN-011 | inventory | Inventario basico | P1 | Planificado | -| MGN-012 | purchasing | Compras basicas | P1 | Planificado | -| MGN-013 | sales | Ventas basicas | P1 | Planificado | -| MGN-014 | crm | CRM basico | P2 | Planificado | -| MGN-015 | projects | Proyectos genericos | P2 | Planificado | +### Fase Foundation (P0) - Modulos Base + +| Codigo | Modulo | Descripcion | Estado | +|--------|--------|-------------|--------| +| MGN-001 | auth | Autenticacion JWT, OAuth, sessions, MFA | En desarrollo | +| MGN-002 | users | Gestion de usuarios CRUD, perfiles | En desarrollo | +| MGN-003 | roles | Roles y permisos (RBAC), feature flags | Planificado | +| MGN-004 | tenants | Multi-tenancy, aislamiento RLS | Planificado | + +### Fase Core Business (P1) - Modulos de Negocio + +| Codigo | Modulo | Descripcion | Estado | +|--------|--------|-------------|--------| +| MGN-005 | catalogs | Catalogos maestros genericos | Planificado | +| MGN-006 | settings | Configuracion del sistema | Planificado | +| MGN-007 | audit | Auditoria y logs | Planificado | +| MGN-008 | notifications | Sistema de notificaciones (email, push, in-app) | Planificado | +| MGN-009 | reports | Reportes genericos y dashboards | Planificado | +| MGN-010 | financial | Contabilidad basica, asientos | Planificado | +| MGN-011 | inventory | Inventario basico, stock | Planificado | +| MGN-012 | purchasing | Compras basicas, ordenes | Planificado | +| MGN-013 | sales | Ventas basicas, facturacion | Planificado | +| MGN-014 | crm | CRM basico, leads, oportunidades | Planificado | +| MGN-015 | projects | Proyectos genericos, tareas | Planificado | + +### Fase SaaS Platform (P2) - Monetizacion e Integraciones + +| Codigo | Modulo | Descripcion | Estado | +|--------|--------|-------------|--------| +| MGN-016 | billing | Suscripciones SaaS, per-seat pricing, cupones | Backlog | +| MGN-017 | payments | Stripe + terminales (MercadoPago, Clip) | Backlog | +| MGN-018 | whatsapp | WhatsApp Business Cloud API, chatbots | Backlog | +| MGN-019 | ai-agents | MCP Server, RAG, pgvector, knowledge bases | Backlog | +| MGN-020 | onboarding | Wizard de onboarding, setup inicial | Backlog | +| MGN-021 | ai-tokens | Sistema de tokens IA, paquetes, consumo | Backlog | + +### Fase Mobile (P3) - Aplicaciones Moviles + +| Codigo | Modulo | Descripcion | Estado | +|--------|--------|-------------|--------| +| MGN-022 | mobile-core | Core React Native: auth, sync, biometrics | Backlog | +| MGN-023 | time-attendance | Checador biometrico + GPS, entradas/salidas | Backlog | +| MGN-024 | worker-profiles | Perfiles de trabajador, modulos por rol | Backlog | --- @@ -144,6 +179,275 @@ ERP Core provee esta funcionalidad comun de forma: | RLS | - | Row-Level Security | | uuid-ossp | - | Generacion UUIDs | | pg_trgm | - | Busqueda fuzzy | +| pgvector | 0.5+ | Embeddings para IA | + +### Mobile +| Tecnologia | Version | Proposito | +|------------|---------|-----------| +| React Native | 0.73+ | Framework mobile | +| Expo | 50+ | Build y deployment | +| WatermelonDB | 0.27+ | SQLite offline | +| react-native-biometrics | 3.x | Huella y Face ID | +| react-native-camera | 4.x | Fotos geolocalizadas | + +### Integraciones +| Tecnologia | Proposito | +|------------|-----------| +| Stripe | Suscripciones y pagos online | +| MercadoPago | Terminales POS fisicas | +| Clip | Terminales POS alternativas | +| WhatsApp Cloud API | Mensajeria y chatbot | +| OpenRouter/OpenAI | LLM para chatbot IA | +| Firebase | Push notifications | +| AWS Rekognition | Reconocimiento facial | + +--- + +## Plataforma SaaS + +### Modelo de Monetizacion + +``` +┌─────────────────────────────────────────────────────────────┐ +│ MODELO PER-SEAT │ +├─────────────────────────────────────────────────────────────┤ +│ Plan Starter │ $99 USD/mes │ 5 usuarios │ Core │ +│ Plan Growth │ $249 USD/mes │ 25 usuarios │ + Reports │ +│ Plan Enterprise │ Custom │ Ilimitados │ + IA + WA │ +├─────────────────────────────────────────────────────────────┤ +│ Usuario extra: $12-15 USD/mes segun plan │ +│ Tokens IA: Paquetes recargables ($29-299) │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Feature Flags por Plan + +| Feature | Starter | Growth | Enterprise | +|---------|---------|--------|------------| +| Modulos Core (Auth, Users, Tenants) | ✅ | ✅ | ✅ | +| Catalogs, Inventory, Sales | ✅ | ✅ | ✅ | +| Reports Avanzados | ❌ | ✅ | ✅ | +| CRM Completo | ❌ | ✅ | ✅ | +| WhatsApp Business | ❌ | ❌ | ✅ | +| Chatbot IA | ❌ | ❌ | ✅ | +| Apps Moviles | ❌ | ✅ | ✅ | +| Checador Biometrico | ❌ | ✅ | ✅ | +| Soporte Prioritario | ❌ | ❌ | ✅ | + +--- + +## Integraciones de Pago + +### Arquitectura de Pagos + +``` +┌─────────────────────────────────────────────────────────────┐ +│ PAYMENT GATEWAY │ +├────────────────────┬────────────────────┬───────────────────┤ +│ STRIPE │ MERCADOPAGO │ CLIP │ +│ (Suscripciones) │ (Terminal POS) │ (Terminal POS) │ +├────────────────────┴────────────────────┴───────────────────┤ +│ - OAuth 2.0 │ - OAuth 2.0 │ - API Keys │ +│ - Webhooks │ - Webhooks │ - Polling │ +│ - Customer Portal │ - Terminales │ - Terminales │ +│ - Invoicing │ - QR Payments │ - Refunds │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Casos de Uso + +1. **Cobro de Suscripcion SaaS:** Stripe (tarjeta guardada, recurrente) +2. **Cobro en Punto de Venta:** MercadoPago/Clip (terminal fisica) +3. **Cobro a Cliente Final:** Link de pago (MercadoPago) +4. **Reembolsos:** Parciales o totales por cualquier proveedor + +--- + +## Chatbot IA (Arquitectura MCP) + +### Modelo MCP Server (Model Context Protocol) + +Basado en el patron de michangarrito, implementamos un chatbot agnostico a proveedores de LLM: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ CANALES DE ENTRADA │ +│ WhatsApp │ Chat Web │ App Movil │ +└─────────┬────────────────┬────────────────┬─────────────────┘ + │ │ │ + └────────────────┼────────────────┘ + │ + ┌──────▼──────┐ + │ MCP SERVER │ + │ (Gateway) │ + └──────┬──────┘ + │ + ┌─────────────────┼─────────────────┐ + │ │ │ + ┌────▼────┐ ┌─────▼─────┐ ┌────▼────┐ + │OpenRouter│ │ Anthropic │ │ Ollama │ + │(Default) │ │ (Claude) │ │ (Local) │ + └─────────┘ └───────────┘ └─────────┘ +``` + +### Componentes del MCP Server + +| Componente | Proposito | +|------------|-----------| +| **Providers** | Adaptadores para OpenRouter, OpenAI, Anthropic, Ollama | +| **Tools** | Funciones que el LLM puede invocar (ventas, inventario, etc.) | +| **Prompts** | System prompts por rol (admin, vendedor, cliente) | +| **RAG Service** | Busqueda en knowledge bases con pgvector | +| **Token Counter** | Tracking de consumo de tokens por usuario | + +### Tools Disponibles para el LLM + +| Tool | Modulo | Descripcion | +|------|--------|-------------| +| `get_sales_summary` | Sales | Resumen de ventas por periodo | +| `check_inventory` | Inventory | Consulta de stock | +| `get_customer_info` | CRM | Informacion de cliente | +| `create_order` | Sales | Crear orden de venta | +| `get_pending_invoices` | Financial | Facturas pendientes | +| `schedule_appointment` | CRM | Agendar cita | +| `generate_report` | Reports | Generar reporte | + +### Sistema de Tokens IA + +``` +Consumo por operacion: +- Consulta simple: ~500 tokens +- Consulta con RAG: ~1,500 tokens +- Generacion de reporte: ~3,000 tokens + +Paquetes recargables: +- Basico: $29 → 1,000 tokens +- Plus: $69 → 3,000 tokens +- Pro: $149 → 8,000 tokens +- Enterprise: $299 → 20,000 tokens +``` + +--- + +## Apps Moviles por Perfil + +### Arquitectura Mobile + +``` +apps/mobile/ +├── packages/ +│ ├── core/ # Auth, API, Storage compartido +│ ├── biometrics/ # Facial, Fingerprint +│ ├── camera/ # Fotos geolocalizadas +│ ├── sync/ # Sincronizacion offline +│ ├── location/ # GPS tracking +│ └── ui/ # Componentes compartidos +├── apps/ +│ ├── admin/ # App Administrador +│ ├── worker/ # App Trabajador (generica) +│ ├── warehouse/ # App Almacenista +│ ├── sales/ # App Vendedor +│ ├── field/ # App Campo (instaladores, tecnicos) +│ └── customer/ # App Cliente Portal +└── package.json +``` + +### Perfiles de Trabajador y Modulos + +| Perfil | Modulos Disponibles | Biometrico | +|--------|---------------------|------------| +| **Administrador** | Todos los modulos | Opcional | +| **Gerente** | Reports, Sales, Inventory, HR | Opcional | +| **Vendedor** | POS, Inventory, CRM, Quotes | Opcional | +| **Almacenista** | Inventory, Purchases, Transfers | Requerido | +| **Operador** | Production, Quality, Orders | Requerido | +| **Tecnico/Instalador** | Work Orders, Photos, Signatures | Requerido | +| **Cliente** | Portal, Orders, Documents | Opcional | + +### Caracteristicas por App + +| Caracteristica | Admin | Worker | Warehouse | Field | +|----------------|-------|--------|-----------|-------| +| Login JWT | ✅ | ✅ | ✅ | ✅ | +| Check-in/out Biometrico | ❌ | ✅ | ✅ | ✅ | +| Geolocalizacion | ❌ | ✅ | ❌ | ✅ | +| Modo Offline | ✅ | ✅ | ✅ | ✅ | +| Fotos con GPS | ❌ | ❌ | ✅ | ✅ | +| Push Notifications | ✅ | ✅ | ✅ | ✅ | +| Dashboard | ✅ | ❌ | ❌ | ❌ | + +--- + +## Sistema de Checador (Time & Attendance) + +### Funcionalidades del Checador + +``` +┌─────────────────────────────────────────────────────────────┐ +│ CHECADOR BIOMETRICO │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ Reconocimiento │ │ Huella │ │ +│ │ Facial │ │ Dactilar │ │ +│ │ (Liveness) │ │ (Nativo) │ │ +│ └────────┬────────┘ └────────┬────────┘ │ +│ │ │ │ +│ └──────────┬───────────┘ │ +│ │ │ +│ ┌───────▼───────┐ │ +│ │ Validacion │ │ +│ │ de Ubicacion │ │ +│ │ (Geocerca) │ │ +│ └───────┬───────┘ │ +│ │ │ +│ ┌───────▼───────┐ │ +│ │ Registro de │ │ +│ │ Entrada/Salida│ │ +│ └───────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Reglas de Negocio del Checador + +1. **Validacion Biometrica:** + - Reconocimiento facial con liveness detection (anti-spoofing) + - Huella dactilar nativa del dispositivo + - Fallback a PIN si biometrico falla + +2. **Validacion de Ubicacion:** + - Geocercas configurables por ubicacion de trabajo + - Radio de tolerancia configurable (50-500m) + - Registro de coordenadas GPS en cada check + +3. **Tipos de Registro:** + - Check-in (entrada) + - Check-out (salida) + - Break start/end (descansos) + - Overtime start/end (horas extra) + +4. **Reportes:** + - Asistencia diaria/semanal/mensual + - Retardos y faltas + - Horas trabajadas vs esperadas + - Incidencias (check fuera de geocerca) + +### Flujo de Check-in + +``` +1. Usuario abre app mobile +2. App verifica si esta en geocerca permitida +3. Si OK, solicita biometrico (facial o huella) +4. Valida identidad con liveness detection +5. Registra entrada con: + - user_id + - timestamp + - coordenadas GPS + - tipo de check (in/out) + - metodo biometrico usado + - foto (opcional) +6. Sincroniza con backend +7. Muestra confirmacion al usuario +``` --- @@ -168,28 +472,40 @@ Un lugar para cada dato. Sincronizacion automatica. ## Entregables por Fase -### Fase 1: Foundation (Actual) -- [ ] MGN-001 Auth completo -- [ ] MGN-002 Users completo -- [ ] MGN-003 Roles completo -- [ ] MGN-004 Tenants completo +### Fase 1: Foundation (En Progreso) +- [ ] MGN-001 Auth completo (JWT, OAuth, MFA) +- [ ] MGN-002 Users completo (CRUD, perfiles) +- [ ] MGN-003 Roles completo (RBAC, feature flags) +- [ ] MGN-004 Tenants completo (RLS, aislamiento) - [ ] Documentacion de todos los modulos ### Fase 2: Core Business - [ ] MGN-005 Catalogs -- [ ] MGN-010 Financial basico -- [ ] MGN-011 Inventory -- [ ] MGN-012 Purchasing -- [ ] MGN-013 Sales - -### Fase 3: Extended - [ ] MGN-006 Settings - [ ] MGN-007 Audit - [ ] MGN-008 Notifications - [ ] MGN-009 Reports +- [ ] MGN-010 Financial basico +- [ ] MGN-011 Inventory +- [ ] MGN-012 Purchasing +- [ ] MGN-013 Sales - [ ] MGN-014 CRM - [ ] MGN-015 Projects +### Fase 3: SaaS Platform +- [ ] MGN-016 Billing (suscripciones per-seat) +- [ ] MGN-017 Payments (Stripe + MercadoPago + Clip) +- [ ] MGN-018 WhatsApp Business +- [ ] MGN-019 AI Agents (MCP Server + RAG) +- [ ] MGN-020 Onboarding +- [ ] MGN-021 AI Tokens + +### Fase 4: Mobile Platform +- [ ] MGN-022 Mobile Core (React Native base) +- [ ] MGN-023 Time & Attendance (Checador) +- [ ] MGN-024 Worker Profiles (perfiles por rol) +- [ ] Apps por vertical (Construccion, Vidrio, Retail, etc.) + --- ## Referencias @@ -199,7 +515,7 @@ Un lugar para cada dato. Sincronizacion automatica. | Directivas | `orchestration/directivas/` | | Patrones Odoo | `orchestration/directivas/DIRECTIVA-PATRONES-ODOO.md` | | Templates | `orchestration/templates/` | -| Catálogo central | `core/catalog/` *(patrones reutilizables)* | +| Catálogo central | `shared/catalog/` *(patrones reutilizables)* | --- @@ -211,7 +527,22 @@ Un lugar para cada dato. Sincronizacion automatica. | Documentacion | 100% antes de desarrollo | | Reutilizacion en verticales | >60% | | Tiempo de setup nueva vertical | <1 semana | +| MRR por tenant (promedio) | $150-300 USD | +| Churn rate mensual | <5% | +| NPS de usuarios | >40 | --- -*Ultima actualizacion: Diciembre 2025* +## Resumen de Modulos + +| Fase | Modulos | Story Points Est. | Estado | +|------|---------|-------------------|--------| +| Foundation | MGN-001 a MGN-004 | 205 SP | En progreso | +| Core Business | MGN-005 a MGN-015 | 192 SP | Planificado | +| SaaS Platform | MGN-016 a MGN-021 | 230 SP | Backlog | +| Mobile Platform | MGN-022 a MGN-024 + Apps | 150 SP | Backlog | +| **Total** | **24 modulos** | **~777 SP** | - | + +--- + +*Ultima actualizacion: Enero 2026* diff --git a/shared/knowledge-base/projects/erp-core/docs/01-analisis-referencias/gamilit/backend-patterns.md b/shared/knowledge-base/projects/erp-core/docs/01-analisis-referencias/gamilit/backend-patterns.md index 690ca5733..e7625ce5f 100644 --- a/shared/knowledge-base/projects/erp-core/docs/01-analisis-referencias/gamilit/backend-patterns.md +++ b/shared/knowledge-base/projects/erp-core/docs/01-analisis-referencias/gamilit/backend-patterns.md @@ -347,7 +347,7 @@ import { validateLoginDto } from '@modules/auth/dtos/login.dto'; "@config/*": ["config/*"], "@database/*": ["database/*"], "@modules/*": ["modules/*"], - "@core/*": ["modules/core/*"], + "@shared/*": ["modules/core/*"], "@accounting/*": ["modules/accounting/*"], "@budgets/*": ["modules/budgets/*"], "@purchasing/*": ["modules/purchasing/*"] diff --git a/shared/knowledge-base/projects/erp-core/docs/02-fase-core-business/MGN-005-catalogs/especificaciones/ET-CATALOG-backend.md b/shared/knowledge-base/projects/erp-core/docs/02-fase-core-business/MGN-005-catalogs/especificaciones/ET-CATALOG-backend.md index 282816984..07ee6d478 100644 --- a/shared/knowledge-base/projects/erp-core/docs/02-fase-core-business/MGN-005-catalogs/especificaciones/ET-CATALOG-backend.md +++ b/shared/knowledge-base/projects/erp-core/docs/02-fase-core-business/MGN-005-catalogs/especificaciones/ET-CATALOG-backend.md @@ -77,7 +77,7 @@ import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm'; -import { TenantEntity } from '@core/entities/tenant.entity'; +import { TenantEntity } from '@shared/entities/tenant.entity'; import { Country } from './country.entity'; import { State } from './state.entity'; import { Currency } from './currency.entity'; @@ -286,7 +286,7 @@ import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm'; -import { TenantEntity } from '@core/entities/tenant.entity'; +import { TenantEntity } from '@shared/entities/tenant.entity'; import { UomCategory } from './uom-category.entity'; export enum UomType { @@ -465,7 +465,7 @@ export class CreateContactDto { // dto/contacts/contact-query.dto.ts import { IsOptional, IsEnum, IsString, IsBoolean, IsArray } from 'class-validator'; import { Transform, Type } from 'class-transformer'; -import { PaginationDto } from '@core/dto/pagination.dto'; +import { PaginationDto } from '@shared/dto/pagination.dto'; import { ContactType, ContactRole } from '../entities/contact.entity'; export class ContactQueryDto extends PaginationDto { @@ -522,8 +522,8 @@ import { ContactTag } from '../entities/contact-tag.entity'; import { CreateContactDto } from '../dto/contacts/create-contact.dto'; import { UpdateContactDto } from '../dto/contacts/update-contact.dto'; import { ContactQueryDto } from '../dto/contacts/contact-query.dto'; -import { TenantContext } from '@core/decorators/tenant.decorator'; -import { PaginatedResult } from '@core/interfaces/pagination.interface'; +import { TenantContext } from '@shared/decorators/tenant.decorator'; +import { PaginatedResult } from '@shared/interfaces/pagination.interface'; @Injectable() export class ContactsService { @@ -764,7 +764,7 @@ import { Repository, LessThanOrEqual } from 'typeorm'; import { CurrencyRate } from '../entities/currency-rate.entity'; import { Currency } from '../entities/currency.entity'; import { CreateRateDto } from '../dto/currencies/create-rate.dto'; -import { TenantContext } from '@core/decorators/tenant.decorator'; +import { TenantContext } from '@shared/decorators/tenant.decorator'; @Injectable() export class CurrencyRatesService { @@ -876,7 +876,7 @@ import { Repository } from 'typeorm'; import { Uom, UomType } from '../entities/uom.entity'; import { UomCategory } from '../entities/uom-category.entity'; import { CreateUomDto } from '../dto/uom/create-uom.dto'; -import { TenantContext } from '@core/decorators/tenant.decorator'; +import { TenantContext } from '@shared/decorators/tenant.decorator'; @Injectable() export class UomService { @@ -1003,7 +1003,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '@modules/auth/guards/jwt-auth.guard'; import { RbacGuard } from '@modules/rbac/guards/rbac.guard'; import { Permissions } from '@modules/rbac/decorators/permissions.decorator'; -import { TenantId } from '@core/decorators/tenant.decorator'; +import { TenantId } from '@shared/decorators/tenant.decorator'; import { ContactsService } from '../services/contacts.service'; import { CreateContactDto } from '../dto/contacts/create-contact.dto'; import { UpdateContactDto } from '../dto/contacts/update-contact.dto'; @@ -1104,7 +1104,7 @@ import { } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '@modules/auth/guards/jwt-auth.guard'; -import { TenantId } from '@core/decorators/tenant.decorator'; +import { TenantId } from '@shared/decorators/tenant.decorator'; import { CurrenciesService } from '../services/currencies.service'; import { CurrencyRatesService } from '../services/currency-rates.service'; import { CreateRateDto } from '../dto/currencies/create-rate.dto'; diff --git a/shared/knowledge-base/projects/erp-core/docs/02-fase-core-business/MGN-005-catalogs/especificaciones/ET-CATALOG-frontend.md b/shared/knowledge-base/projects/erp-core/docs/02-fase-core-business/MGN-005-catalogs/especificaciones/ET-CATALOG-frontend.md index 946fcad2b..5f337b0aa 100644 --- a/shared/knowledge-base/projects/erp-core/docs/02-fase-core-business/MGN-005-catalogs/especificaciones/ET-CATALOG-frontend.md +++ b/shared/knowledge-base/projects/erp-core/docs/02-fase-core-business/MGN-005-catalogs/especificaciones/ET-CATALOG-frontend.md @@ -283,7 +283,7 @@ import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; import { Contact, ContactFilters, CreateContactDto } from '../types/contact.types'; import { contactsService } from '../services/contacts.service'; -import { PaginatedResult } from '@core/types/pagination'; +import { PaginatedResult } from '@shared/types/pagination'; interface ContactsState { // Data diff --git a/shared/knowledge-base/projects/erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN.md b/shared/knowledge-base/projects/erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN.md index c0ae5f778..443abbbd5 100644 --- a/shared/knowledge-base/projects/erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN.md +++ b/shared/knowledge-base/projects/erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN.md @@ -303,7 +303,7 @@ CREATE TRIGGER trg_update_blocked_state ```typescript // src/modules/projects/domain/entities/task-dependency.entity.ts -import { Entity, AggregateRoot } from '@core/domain'; +import { Entity, AggregateRoot } from '@shared/domain'; export enum DependencyType { FINISH_TO_START = 'finish_to_start', diff --git a/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-019-ai-agents.md b/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-019-ai-agents.md index 6f76f45d7..a8504f6c9 100644 --- a/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-019-ai-agents.md +++ b/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-019-ai-agents.md @@ -5,30 +5,175 @@ | Campo | Valor | |-------|-------| | **ID** | EPIC-MGN-019 | -| **Nombre** | AI Agents (RAG + Tools) | +| **Nombre** | AI Agents (MCP Server + RAG + Tools) | | **Modulo** | ai-agents | | **Fase** | Fase 4 - SaaS Platform | -| **Prioridad** | P3 | +| **Prioridad** | P2 | | **Estado** | Backlog | -| **Story Points** | 55 | +| **Story Points** | 68 | | **Sprint(s)** | Sprint 26-30 | --- ## Descripcion -Sistema de agentes inteligentes con RAG (Retrieval Augmented Generation) usando pgvector para embeddings. Permite crear agentes configurables por tenant, knowledge bases con documentos, definicion de tools/funciones, conversaciones con historial y ejecucion de tools en tiempo real. +Sistema de agentes inteligentes basado en **MCP Server (Model Context Protocol)** con RAG (Retrieval Augmented Generation) usando pgvector para embeddings. Implementa un gateway agnostico a proveedores de LLM (OpenRouter, OpenAI, Anthropic, Ollama) siguiendo el patron de michangarrito. Permite crear agentes configurables por tenant, knowledge bases con documentos, definicion de tools/funciones, conversaciones con historial y ejecucion de tools en tiempo real a traves de multiples canales (WhatsApp, Chat Web, App Movil). --- ## Objetivo de Negocio Proveer agentes AI que: -- Automaticen atencion al cliente -- Respondan preguntas basadas en documentacion -- Ejecuten acciones en el sistema +- Automaticen atencion al cliente via WhatsApp, chat web y app movil +- Respondan preguntas basadas en documentacion (RAG) +- Ejecuten acciones en el sistema (tools) - Escalen soporte sin aumentar personal - Mejoren experiencia del usuario +- Generen reportes y consultas de datos conversacionalmente +- Sean agnosticos al proveedor de LLM (OpenRouter, OpenAI, Anthropic, Ollama) + +--- + +## Arquitectura MCP Server (Model Context Protocol) + +### Vision General + +``` +┌─────────────────────────────────────────────────────────────┐ +│ CANALES DE ENTRADA │ +│ WhatsApp Cloud API │ Chat Web │ App Movil │ +└────────────┬──────────────────┬──────────────┬──────────────┘ + │ │ │ + └──────────────────┼──────────────┘ + │ + ┌──────▼──────┐ + │ MCP SERVER │ + │ Gateway │ + └──────┬──────┘ + │ + ┌─────────────────┼─────────────────┐ + │ │ │ + ┌────▼────┐ ┌─────▼─────┐ ┌────▼────┐ + │OpenRouter│ │ Anthropic │ │ Ollama │ + │(Default) │ │ (Claude) │ │ (Local) │ + └─────────┘ └───────────┘ └─────────┘ +``` + +### Componentes del MCP Server + +``` +apps/backend/src/modules/mcp-server/ +├── providers/ # Adaptadores LLM +│ ├── base.provider.ts # Interface comun +│ ├── openrouter.provider.ts # OpenRouter (default) +│ ├── openai.provider.ts # OpenAI directo +│ ├── anthropic.provider.ts # Anthropic Claude +│ └── ollama.provider.ts # Ollama local +│ +├── tools/ # Herramientas disponibles +│ ├── sales.tools.ts # get_sales, create_order +│ ├── inventory.tools.ts # check_stock, get_alerts +│ ├── crm.tools.ts # get_customer, create_lead +│ ├── financial.tools.ts # get_invoices, get_balance +│ ├── reports.tools.ts # generate_report +│ └── hr.tools.ts # get_attendance, get_employee +│ +├── prompts/ # System prompts por rol +│ ├── base.prompt.ts # Prompt base +│ ├── sales.prompt.ts # Vendedor +│ ├── manager.prompt.ts # Gerente +│ ├── support.prompt.ts # Soporte +│ └── customer.prompt.ts # Cliente externo +│ +├── rag/ # Retrieval Augmented Generation +│ ├── embedding.service.ts # Generacion de embeddings +│ ├── chunker.service.ts # Particion de documentos +│ └── retriever.service.ts # Busqueda en pgvector +│ +├── handlers/ # Procesamiento +│ ├── message.handler.ts # Procesa mensaje entrante +│ └── tool-call.handler.ts # Ejecuta tools invocadas +│ +├── mcp.module.ts +└── mcp.service.ts +``` + +### Flujo de Procesamiento + +``` +1. MENSAJE ENTRANTE (WhatsApp/Chat/App) + "¿Cuanto vendimos esta semana?" + │ + ▼ +2. MESSAGE HANDLER + - Identifica tenant y usuario + - Carga historial de conversacion (Redis) + - Obtiene contexto del usuario (rol, permisos) + │ + ▼ +3. RAG SERVICE (si aplica) + - Genera embedding de la pregunta + - Busca chunks similares en pgvector + - Obtiene top K documentos relevantes + │ + ▼ +4. PROMPT BUILDER + - Selecciona system prompt por rol + - Inyecta contexto de RAG + - Inyecta historial de conversacion + - Lista tools disponibles segun permisos + │ + ▼ +5. LLM PROVIDER + - Envia al provider configurado (OpenRouter/OpenAI/etc) + - Recibe respuesta + tool_calls + │ + ▼ +6. TOOL CALL HANDLER (si hay tool_calls) + - Valida parametros + - Ejecuta tool contra Backend API + - Retorna resultado al LLM + - LLM genera respuesta final + │ + ▼ +7. RESPONSE + - Guarda en historial + - Registra consumo de tokens + - Envia respuesta al canal de origen +``` + +### Tools Disponibles por Modulo + +| Tool | Modulo | Parametros | Descripcion | +|------|--------|------------|-------------| +| `get_sales_summary` | Sales | periodo, filtros | Resumen de ventas | +| `get_top_products` | Sales | periodo, limit | Productos mas vendidos | +| `create_quote` | Sales | cliente, items | Crear cotizacion | +| `check_inventory` | Inventory | product_id, warehouse | Consultar stock | +| `get_stock_alerts` | Inventory | - | Productos con stock bajo | +| `get_customer_info` | CRM | customer_id | Info de cliente | +| `create_lead` | CRM | name, phone, source | Crear lead | +| `get_pending_invoices` | Financial | customer_id, days | Facturas vencidas | +| `get_balance` | Financial | account_id | Saldo de cuenta | +| `generate_report` | Reports | type, periodo | Generar reporte PDF | +| `get_attendance` | HR | employee_id, fecha | Asistencia de empleado | +| `schedule_meeting` | CRM | participants, datetime | Agendar reunion | + +### Configuracion de Providers + +```typescript +// .env configuration +LLM_PROVIDER=openrouter // openrouter | openai | anthropic | ollama +LLM_API_KEY=sk-xxx +LLM_MODEL=anthropic/claude-3-haiku // Modelo a usar +LLM_BASE_URL=https://openrouter.ai/api/v1 +LLM_MAX_TOKENS=4096 +LLM_TEMPERATURE=0.7 + +// Fallback chain +LLM_FALLBACK_PROVIDER=ollama // Si falla el principal +LLM_FALLBACK_MODEL=llama3 +``` --- @@ -148,3 +293,5 @@ Proveer agentes AI que: **Creada por:** Requirements-Analyst **Fecha:** 2025-12-05 +**Actualizada por:** Orquestador +**Ultima actualizacion:** 2026-01-04 (Arquitectura MCP Server agregada) diff --git a/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-019-mobile-apps.md b/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-019-mobile-apps.md index 6e2a66f0f..9402e3d15 100644 --- a/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-019-mobile-apps.md +++ b/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-019-mobile-apps.md @@ -1,23 +1,24 @@ -# EPICA: EPIC-MGN-019 - Apps Moviles por Perfil +# EPICA: EPIC-MGN-019B - Apps Moviles por Perfil ## Metadata | Campo | Valor | |-------|-------| -| **ID** | EPIC-MGN-019 | +| **ID** | EPIC-MGN-019B | | **Nombre** | Apps Moviles por Perfil de Usuario | | **Modulo** | mobile | -| **Fase** | Fase 5 - Mobile Platform | +| **Fase** | Fase 4 - Mobile Platform | | **Prioridad** | P1 | | **Estado** | Backlog | -| **Story Points** | 89 | -| **Sprint(s)** | Sprint 29-36 | +| **Story Points** | 110 | +| **Sprint(s)** | Sprint 29-40 | +| **Relacionado** | MGN-022 (Mobile Core), MGN-023 (Time & Attendance), MGN-024 (Worker Profiles) | --- ## Descripcion -Desarrollo de apps moviles React Native para diferentes perfiles de usuario en cada vertical. Incluye funcionalidades biometricas (reconocimiento facial, huella), modo offline, y sincronizacion con backend. +Desarrollo de apps moviles React Native para diferentes perfiles de usuario en cada vertical. Cada perfil de trabajador tiene acceso a modulos y herramientas especificos segun su rol. Incluye funcionalidades biometricas (reconocimiento facial, huella), modo offline con sincronizacion, checador integrado, y sistema de perfiles dinamicos que determina que modulos y herramientas estan disponibles para cada usuario. --- @@ -25,8 +26,97 @@ Desarrollo de apps moviles React Native para diferentes perfiles de usuario en c - Digitalizar operaciones en campo - Capturar datos en tiempo real -- Validar asistencia con biometricos +- Validar asistencia con biometricos (checador) - Aumentar productividad de usuarios moviles +- Proporcionar herramientas especificas por perfil de trabajador +- Permitir trabajo offline con sincronizacion automatica +- Integrar chatbot IA para consultas rapidas + +--- + +## Sistema de Perfiles de Trabajador + +### Arquitectura de Perfiles + +Cada usuario tiene asignado uno o mas perfiles que determinan: +1. **Modulos accesibles:** Que secciones de la app puede ver +2. **Acciones permitidas:** Que operaciones puede realizar +3. **Biometrico requerido:** Si necesita validacion biometrica +4. **Checador integrado:** Si debe registrar entradas/salidas +5. **Geocerca:** Si esta restringido a ubicaciones especificas + +### Perfiles Disponibles + +| Perfil | Codigo | Modulos | Biometrico | Checador | +|--------|--------|---------|------------|----------| +| **Administrador** | ADMIN | Todos | Opcional | No | +| **Gerente** | MANAGER | Reports, Sales, Inventory, HR, CRM | Opcional | No | +| **Vendedor** | SALES | POS, Inventory, CRM, Quotes, Customers | Opcional | Configurable | +| **Almacenista** | WAREHOUSE | Inventory, Purchases, Transfers, Receiving | Requerido | Si | +| **Operador** | OPERATOR | Production, Quality, Work Orders | Requerido | Si | +| **Tecnico/Instalador** | FIELD | Work Orders, Photos, Signatures, GPS | Requerido | Si + GPS | +| **Repartidor** | DELIVERY | Deliveries, Routes, POD, GPS | Requerido | Si + GPS | +| **Cliente Portal** | CUSTOMER | Orders, Documents, Tickets, Payments | Opcional | No | + +### Modulos por Perfil + +``` +┌─────────────────────────────────────────────────────────────┐ +│ MODULOS DISPONIBLES │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ CHECADOR │ │ POS │ │ INVENTARIO │ │ +│ │ Check-in/out │ │ Punto venta │ │ Stock/Transf │ │ +│ │ Biometrico │ │ Cobros │ │ Conteos │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ ORDENES │ │ FOTOS │ │ REPORTES │ │ +│ │ Work orders │ │ Geolocalizad │ │ Dashboards │ │ +│ │ Asignaciones │ │ Evidencias │ │ KPIs │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ CHATBOT │ │ ENTREGAS │ │ CLIENTES │ │ +│ │ Asistente IA │ │ Rutas │ │ CRM basico │ │ +│ │ Consultas │ │ POD/Firmas │ │ Historial │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Configuracion de Perfiles (Backend) + +```typescript +// Ejemplo de configuracion de perfil +{ + "code": "WAREHOUSE", + "name": "Almacenista", + "modules": [ + { "id": "checador", "required": true }, + { "id": "inventory", "required": true }, + { "id": "purchases", "required": false }, + { "id": "transfers", "required": true }, + { "id": "receiving", "required": true }, + { "id": "chatbot", "required": false } + ], + "biometric": { + "required": true, + "methods": ["fingerprint", "facial"], + "fallback_pin": true + }, + "attendance": { + "required": true, + "geofence_required": true, + "photo_required": false + }, + "permissions": [ + "inventory.view", "inventory.count", "inventory.transfer", + "purchases.receive", "checador.check_in", "checador.check_out" + ] +} +``` --- @@ -157,3 +247,5 @@ apps/mobile/ **Creado por:** Requirements-Analyst **Fecha:** 2025-12-05 +**Actualizado por:** Orquestador +**Ultima actualizacion:** 2026-01-04 (Sistema de perfiles de trabajador detallado) diff --git a/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-023-time-attendance.md b/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-023-time-attendance.md new file mode 100644 index 000000000..78b13e3da --- /dev/null +++ b/shared/knowledge-base/projects/erp-core/docs/08-epicas/EPIC-MGN-023-time-attendance.md @@ -0,0 +1,355 @@ +# EPICA: EPIC-MGN-023 - Time & Attendance (Checador) + +## Metadata + +| Campo | Valor | +|-------|-------| +| **ID** | EPIC-MGN-023 | +| **Nombre** | Time & Attendance - Checador Biometrico con GPS | +| **Modulo** | time-attendance | +| **Fase** | Fase 4 - Mobile Platform | +| **Prioridad** | P1 | +| **Estado** | Backlog | +| **Story Points** | 55 | +| **Sprint(s)** | Sprint 37-40 | + +--- + +## Descripcion + +Sistema de control de asistencia (Time & Attendance) con validacion biometrica (reconocimiento facial y huella dactilar) y geolocalizacion. Permite registrar entradas, salidas, descansos y horas extra con validacion de ubicacion mediante geocercas configurables por sitio de trabajo. + +--- + +## Objetivo de Negocio + +Proveer un sistema de checador que: +- Elimine el fraude en registro de asistencia (buddy punching) +- Valide que el trabajador este fisicamente en el sitio +- Automatice el calculo de horas trabajadas +- Genere reportes de asistencia en tiempo real +- Integre con nomina para calculo automatico +- Funcione offline con sincronizacion posterior + +--- + +## Stakeholders + +| Rol | Nombre/Equipo | Responsabilidad | +|-----|---------------|-----------------| +| Product Owner | Equipo Producto | Definicion de reglas de negocio | +| Tech Lead | Equipo Mobile | Arquitectura biometrica y GPS | +| RRHH | Recursos Humanos | Definicion de politicas de asistencia | +| Supervisor | Operaciones | Validacion de incidencias | +| Trabajador | Usuario final | Uso diario del checador | + +--- + +## Historias de Usuario + +| ID | Historia | Prioridad | SP | Estado | +|----|----------|-----------|-----|--------| +| US-MGN023-001 | Como admin, quiero configurar geocercas por ubicacion de trabajo | P0 | 5 | Backlog | +| US-MGN023-002 | Como admin, quiero definir horarios de trabajo por empleado/grupo | P0 | 5 | Backlog | +| US-MGN023-003 | Como trabajador, quiero hacer check-in con reconocimiento facial | P0 | 8 | Backlog | +| US-MGN023-004 | Como trabajador, quiero hacer check-in con huella dactilar | P0 | 5 | Backlog | +| US-MGN023-005 | Como trabajador, quiero hacer check-out al terminar mi jornada | P0 | 3 | Backlog | +| US-MGN023-006 | Como trabajador, quiero registrar inicio/fin de descanso | P1 | 3 | Backlog | +| US-MGN023-007 | Como trabajador, quiero registrar horas extra | P1 | 3 | Backlog | +| US-MGN023-008 | Como supervisor, quiero ver asistencia en tiempo real de mi equipo | P0 | 5 | Backlog | +| US-MGN023-009 | Como supervisor, quiero aprobar/rechazar incidencias | P1 | 5 | Backlog | +| US-MGN023-010 | Como admin, quiero generar reportes de asistencia | P0 | 5 | Backlog | +| US-MGN023-011 | Como trabajador, quiero que mi check funcione sin internet | P0 | 8 | Backlog | + +**Total Story Points:** 55 SP + +--- + +## Criterios de Aceptacion de la Epica + +**Funcionales:** +- [ ] Geocercas configurables (poligonos o circulos) por ubicacion +- [ ] Radio de tolerancia configurable (50-500m) +- [ ] Reconocimiento facial con liveness detection (anti-spoofing) +- [ ] Huella dactilar usando biometrico nativo del dispositivo +- [ ] Fallback a PIN si biometrico no disponible +- [ ] Registro de coordenadas GPS en cada check +- [ ] Foto opcional al momento del check +- [ ] Tipos de check: entrada, salida, descanso_inicio, descanso_fin, extra_inicio, extra_fin +- [ ] Calculo automatico de horas trabajadas +- [ ] Deteccion de retardos segun horario configurado +- [ ] Modo offline con sincronizacion al recuperar conexion +- [ ] Resolucion de conflictos de sincronizacion + +**No Funcionales:** +- [ ] Tiempo de reconocimiento facial < 2 segundos +- [ ] Precision de GPS < 10 metros +- [ ] Almacenamiento offline de hasta 7 dias de checks +- [ ] Sincronizacion automatica en background +- [ ] Bateria: bajo consumo de GPS (sampling inteligente) +- [ ] Seguridad: datos biometricos nunca salen del dispositivo + +**Tecnicos:** +- [ ] Cobertura de tests > 80% +- [ ] Integracion con AWS Rekognition o Azure Face +- [ ] Soporte Android 9+ e iOS 14+ +- [ ] WatermelonDB para persistencia offline + +--- + +## Arquitectura Tecnica + +### Flujo de Check-in + +``` +┌─────────────────────────────────────────────────────────────┐ +│ FLUJO DE CHECK-IN │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Usuario abre app │ +│ │ │ +│ ▼ │ +│ 2. Obtener ubicacion GPS │ +│ │ │ +│ ▼ │ +│ 3. Validar geocerca ─────────────────┐ │ +│ │ │ │ +│ │ (Dentro) │ (Fuera) │ +│ ▼ ▼ │ +│ 4. Solicitar biometrico Mostrar error │ +│ (Facial o Huella) "Fuera de ubicacion" │ +│ │ │ +│ ▼ │ +│ 5. Validar identidad │ +│ (Liveness detection) │ +│ │ │ +│ │ (OK) │ +│ ▼ │ +│ 6. Capturar foto (opcional) │ +│ │ │ +│ ▼ │ +│ 7. Crear registro local │ +│ (WatermelonDB) │ +│ │ │ +│ ▼ │ +│ 8. Sincronizar con backend │ +│ (si hay conexion) │ +│ │ │ +│ ▼ │ +│ 9. Mostrar confirmacion │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Modelo de Datos + +``` +┌────────────────────┐ ┌────────────────────┐ +│ work_locations │ │ work_schedules │ +├────────────────────┤ ├────────────────────┤ +│ id │ │ id │ +│ tenant_id │ │ tenant_id │ +│ name │ │ name │ +│ address │ │ work_location_id │ +│ geofence_type │◄─────│ monday_start │ +│ geofence_polygon │ │ monday_end │ +│ geofence_radius │ │ tuesday_start... │ +│ tolerance_meters │ │ break_duration_min │ +│ is_active │ │ is_active │ +└────────────────────┘ └────────────────────┘ + │ │ + │ │ + ▼ ▼ +┌────────────────────────────────────────────────┐ +│ time_attendance_records │ +├────────────────────────────────────────────────┤ +│ id │ +│ tenant_id │ +│ user_id │ +│ work_location_id │ +│ work_schedule_id │ +│ check_type (in/out/break_start/break_end/...) │ +│ check_timestamp │ +│ latitude │ +│ longitude │ +│ accuracy_meters │ +│ biometric_method (facial/fingerprint/pin) │ +│ photo_url │ +│ device_id │ +│ is_synced │ +│ sync_timestamp │ +│ is_valid │ +│ validation_notes │ +│ created_at │ +└────────────────────────────────────────────────┘ + │ + │ + ▼ +┌────────────────────────────────────────────────┐ +│ attendance_incidents │ +├────────────────────────────────────────────────┤ +│ id │ +│ tenant_id │ +│ user_id │ +│ incident_date │ +│ incident_type (late/early_leave/absent/...) │ +│ expected_time │ +│ actual_time │ +│ difference_minutes │ +│ status (pending/approved/rejected) │ +│ supervisor_id │ +│ supervisor_notes │ +│ created_at │ +└────────────────────────────────────────────────┘ +``` + +--- + +## Dependencias + +**Esta epica depende de:** +| Epica/Modulo | Estado | Bloqueante | +|--------------|--------|------------| +| EPIC-MGN-001 Auth | Ready | Si | +| EPIC-MGN-002 Users | Ready | Si | +| EPIC-MGN-004 Tenants | Ready | Si | +| EPIC-MGN-022 Mobile Core | Backlog | Si | +| AWS Rekognition / Azure Face | External | Si | +| Google Maps / Mapbox | External | Si (geocercas) | + +**Esta epica bloquea:** +| Epica/Modulo | Razon | +|--------------|-------| +| Integracion con Nomina | Calculo de horas trabajadas | +| HR Analytics | Metricas de asistencia | + +--- + +## Desglose Tecnico + +**Database:** +- [ ] Schema: `hr` +- [ ] Tablas: 5 (work_locations, work_schedules, schedule_assignments, time_attendance_records, attendance_incidents) +- [ ] Funciones: calculate_worked_hours, detect_incidents +- [ ] Triggers: auto_create_incident (retardos, faltas) +- [ ] Indices: Por user_id, check_timestamp, work_location_id +- [ ] RLS Policies: Si (por tenant) + +**Backend:** +- [ ] Modulo: `time-attendance` +- [ ] Services: AttendanceService, GeofenceService, IncidentService, ReportService +- [ ] Controllers: AttendanceController, LocationsController, SchedulesController, IncidentsController +- [ ] Endpoints: 20+ (check in/out, locations CRUD, schedules CRUD, incidents, reports) +- [ ] Jobs: DailyIncidentDetectorJob, AttendanceSummaryJob +- [ ] Tests: 40+ + +**Mobile (React Native):** +- [ ] Screens: 5 (CheckIn, CheckHistory, LocationMap, IncidentsList, Settings) +- [ ] Components: BiometricPrompt, GeofenceValidator, SyncIndicator, CheckConfirmation +- [ ] Hooks: useGeolocation, useBiometrics, useOfflineSync +- [ ] Storage: WatermelonDB schemas para offline +- [ ] Services: AttendanceSync, LocationTracker + +**Frontend (Admin):** +- [ ] Paginas: 6 (Locations, Schedules, Attendance, Incidents, Reports, Settings) +- [ ] Componentes: GeofenceEditor, ScheduleCalendar, AttendanceTable, IncidentApproval +- [ ] Stores: 1 (attendanceStore) + +--- + +## Reportes + +| Reporte | Descripcion | Frecuencia | +|---------|-------------|------------| +| Asistencia Diaria | Lista de checks por dia | Diario | +| Resumen Semanal | Horas trabajadas por empleado | Semanal | +| Incidencias Pendientes | Retardos/faltas sin aprobar | Diario | +| Comparativo Horarios | Esperado vs Real | Mensual | +| Costo de Horas Extra | Horas extra por departamento | Quincenal | + +--- + +## Integraciones + +### Reconocimiento Facial + +| Proveedor | Pros | Contras | +|-----------|------|---------| +| AWS Rekognition | Alta precision, liveness detection | Costo por uso | +| Azure Face | Buena precision, anti-spoofing | Requiere Azure | +| Google Vision | Integracion con Firebase | Menos features | +| On-device (ML Kit) | Sin costo de API, privacidad | Menor precision | + +**Recomendacion:** AWS Rekognition con fallback a ML Kit on-device. + +### Geofencing + +| Proveedor | Uso | +|-----------|-----| +| Google Maps | Geocoding, visualizacion de mapas | +| Mapbox | Alternativa a Google Maps | +| react-native-geofencing | Deteccion de entrada/salida | + +--- + +## Riesgos + +| Riesgo | Probabilidad | Impacto | Mitigacion | +|--------|--------------|---------|------------| +| GPS impreciso en interiores | Alta | Medio | Tolerancia configurable, WiFi positioning | +| Fallo en reconocimiento facial | Media | Alto | Fallback a huella y PIN | +| Sincronizacion fallida | Media | Alto | Retry automatico, alertas | +| Bateria agotada rapidamente | Media | Medio | Sampling inteligente de GPS | +| Spoofing de foto | Baja | Critico | Liveness detection obligatorio | + +--- + +## Definition of Ready (DoR) + +- [x] Historias de usuario definidas +- [x] Criterios de aceptacion claros +- [x] Dependencias identificadas +- [x] Estimacion completada +- [ ] Cuenta AWS/Azure para biometricos +- [ ] API Key de Google Maps/Mapbox +- [ ] Politicas de asistencia definidas por RRHH +- [x] Sin bloqueadores activos + +## Definition of Done (DoD) + +- [ ] Codigo implementado y revisado +- [ ] Tests pasando (unit, integration, e2e) +- [ ] Check-in/out funcionando con facial +- [ ] Check-in/out funcionando con huella +- [ ] Geocercas validando correctamente +- [ ] Modo offline funcionando +- [ ] Sincronizacion automatica +- [ ] Reportes generandose +- [ ] Documentacion actualizada +- [ ] Inventarios actualizados +- [ ] Demo realizada +- [ ] Product Owner aprobo + +--- + +## Documentacion Relacionada + +- Vision General: `docs/00-vision-general/VISION-ERP-CORE.md` +- Requerimientos: `docs/03-requerimientos/RF-time-attendance/` +- User Stories: `docs/05-user-stories/mgn-023/` +- DDL Spec: `docs/04-modelado/database-design/DDL-SPEC-hr_attendance.md` +- Mobile Core: `EPIC-MGN-022-mobile-core.md` + +--- + +## Historial + +| Fecha | Cambio | Autor | +|-------|--------|-------| +| 2026-01-04 | Creacion de epica | Orquestador | + +--- + +**Creada por:** Orquestador +**Fecha:** 2026-01-04 +**Ultima actualizacion:** 2026-01-04 diff --git a/shared/knowledge-base/projects/erp-core/docs/README.md b/shared/knowledge-base/projects/erp-core/docs/README.md index a47dfb350..c6d741662 100644 --- a/shared/knowledge-base/projects/erp-core/docs/README.md +++ b/shared/knowledge-base/projects/erp-core/docs/README.md @@ -113,16 +113,26 @@ Crear el **ERP Genérico** como base reutilizable (60-70%) para los 3 ERPs espec | **MGN-013** | Portal de Usuarios | portal | | **MGN-014** | Mensajería y Notificaciones | mail | -### Fase SaaS Platform (4 módulos - Prioridad P3) +### Fase SaaS Platform (6 módulos - Prioridad P2) | Código | Nombre | Descripción | |--------|--------|-------------| | **MGN-016** | Billing SaaS | Per-seat pricing, suscripciones, feature flags | -| **MGN-017** | Payments POS | MercadoPago, Clip, terminales de pago | +| **MGN-017** | Payments | Stripe (suscripciones) + MercadoPago/Clip (terminales POS) | | **MGN-018** | WhatsApp Business | Cloud API, chatbots, campañas | -| **MGN-019** | AI Agents | RAG, pgvector, knowledge bases, tools | +| **MGN-019** | AI Agents | MCP Server, RAG, pgvector, knowledge bases, tools | +| **MGN-020** | Onboarding | Wizard de setup inicial, configuración guiada | +| **MGN-021** | AI Tokens | Sistema de tokens IA, paquetes recargables | -**Total:** 19 módulos (14 core + 1 projects + 4 SaaS) +### Fase Mobile Platform (3 módulos - Prioridad P1) + +| Código | Nombre | Descripción | +|--------|--------|-------------| +| **MGN-022** | Mobile Core | React Native base: auth, sync, biometrics | +| **MGN-023** | Time & Attendance | Checador biométrico + GPS, entradas/salidas | +| **MGN-024** | Worker Profiles | Perfiles de trabajador, módulos por rol | + +**Total:** 24 módulos (14 core + 6 SaaS + 3 mobile + apps por vertical) --- @@ -354,7 +364,42 @@ Ver directiva: `/workspace/core/orchestration/directivas/DIRECTIVA-ESTRUCTURA-DO --- -**Última actualización:** 2025-12-05 -**Coordinador:** Architecture-Analyst +**Última actualización:** 2026-01-04 +**Coordinador:** Architecture-Analyst / Orquestador **Estado:** 📋 Fase 2 - Modelado en progreso -**Próximo paso:** Completar especificaciones técnicas de módulos SaaS +**Próximo paso:** Implementar módulos SaaS Platform y Mobile Platform + +--- + +## NUEVAS CARACTERÍSTICAS (Enero 2026) + +Se han agregado las siguientes características a la visión del proyecto: + +### Plataforma SaaS Completa +- Modelo de monetización per-seat con 3 planes (Starter, Growth, Enterprise) +- Feature flags por plan para controlar acceso a módulos + +### Integraciones de Pago +- **Stripe:** Suscripciones recurrentes, webhooks, customer portal +- **MercadoPago:** Terminales POS físicas, QR, links de pago +- **Clip:** Terminales POS alternativas + +### Chatbot IA (MCP Server) +- Arquitectura agnóstica a proveedores LLM (OpenRouter, OpenAI, Anthropic, Ollama) +- RAG con pgvector para knowledge bases +- Tools que ejecutan acciones en el sistema +- Integración con WhatsApp, Chat Web y App Móvil + +### Apps Móviles por Perfil +- React Native con Expo +- Perfiles de trabajador con módulos específicos +- Biométricos (facial y huella) +- Modo offline con sincronización + +### Sistema de Checador (Time & Attendance) +- Validación biométrica con liveness detection +- Geocercas configurables por ubicación +- Registro de entradas/salidas/descansos +- Reportes de asistencia + +Ver detalles completos en: [VISION-ERP-CORE.md](00-vision-general/VISION-ERP-CORE.md) diff --git a/shared/knowledge-base/projects/gamilit/README.md b/shared/knowledge-base/projects/gamilit/README.md index 643ec3bcc..4de48e992 100644 --- a/shared/knowledge-base/projects/gamilit/README.md +++ b/shared/knowledge-base/projects/gamilit/README.md @@ -37,11 +37,11 @@ Los siguientes patrones fueron extraidos de gamilit al catalogo: | Patron | Ubicacion Catalogo | |--------|-------------------| -| Auth + Sessions | `core/catalog/auth/` | -| Multi-tenancy | `core/catalog/multi-tenancy/` | -| Notifications | `core/catalog/notifications/` | -| Rate Limiting | `core/catalog/rate-limiting/` | -| Feature Flags | `core/catalog/feature-flags/` | +| Auth + Sessions | `shared/catalog/auth/` | +| Multi-tenancy | `shared/catalog/multi-tenancy/` | +| Notifications | `shared/catalog/notifications/` | +| Rate Limiting | `shared/catalog/rate-limiting/` | +| Feature Flags | `shared/catalog/feature-flags/` | ## Lecciones Aprendidas @@ -54,7 +54,7 @@ Los siguientes patrones fueron extraidos de gamilit al catalogo: - Proyecto: `projects/gamilit/` - Legacy: `shared/knowledge-base/reference/erp-inmobiliaria-legacy/gamilit/` -- Catalogo: `core/catalog/` (modulos extraidos) +- Catalogo: `shared/catalog/` (modulos extraidos) --- diff --git a/shared/knowledge-base/projects/inmobiliaria-analytics/ML-SERVICES-SPEC.yml b/shared/knowledge-base/projects/inmobiliaria-analytics/ML-SERVICES-SPEC.yml new file mode 100644 index 000000000..fa64c6310 --- /dev/null +++ b/shared/knowledge-base/projects/inmobiliaria-analytics/ML-SERVICES-SPEC.yml @@ -0,0 +1,1467 @@ +# ============================================================================ +# ESPECIFICACION: Servicios ML para Analytics Inmobiliario SaaS +# ============================================================================ +# Fecha: 2026-01-04 +# Version: 1.0.0 +# Autor: ML-Specialist-Agent +# Dominio: Real Estate Analytics / Proptech +# Tipo: Documento de Especificacion Tecnica +# ============================================================================ + +metadata: + proyecto: inmobiliaria-analytics-saas + version: "1.0.0" + fecha_creacion: "2026-01-04" + autor: "ML-Specialist-Agent" + estado: "draft" + categoria: "ML/AI Services" + referencias: + - "PERFIL-ML-SPECIALIST.md" + - "SIMCO-ML.md" + - "trading-platform/apps/ml-engine (patron de referencia)" + +# ============================================================================ +# 1. MODELOS DE PREDICCION +# ============================================================================ + +modelos_prediccion: + + # -------------------------------------------------------------------------- + # 1.1 Prediccion de Precios de Propiedades (AVM - Automated Valuation Model) + # -------------------------------------------------------------------------- + prediccion_precios: + nombre: "PropertyPricePredictor" + alias: "AVM-Core" + tipo: "regression" + descripcion: | + Modelo de valuacion automatica de propiedades basado en caracteristicas + fisicas, ubicacion, mercado y comparables cercanos. + + features: + intrinsecas: + - nombre: "superficie_construida_m2" + tipo: float + importancia: "alta" + - nombre: "superficie_terreno_m2" + tipo: float + importancia: "alta" + - nombre: "num_recamaras" + tipo: int + importancia: "media" + - nombre: "num_banos" + tipo: float # Permite 1.5, 2.5 banos + importancia: "media" + - nombre: "num_estacionamientos" + tipo: int + importancia: "media" + - nombre: "antiguedad_anos" + tipo: int + importancia: "alta" + - nombre: "tipo_propiedad" + tipo: categorical # casa, depto, terreno, local, bodega + importancia: "alta" + - nombre: "estado_conservacion" + tipo: ordinal # 1-5 (malo a excelente) + importancia: "media" + - nombre: "tiene_alberca" + tipo: boolean + importancia: "baja" + - nombre: "tiene_jardin" + tipo: boolean + importancia: "baja" + - nombre: "nivel_piso" + tipo: int # Para departamentos + importancia: "media" + - nombre: "amenidades_count" + tipo: int + importancia: "media" + + ubicacion: + - nombre: "latitud" + tipo: float + importancia: "alta" + - nombre: "longitud" + tipo: float + importancia: "alta" + - nombre: "codigo_postal" + tipo: categorical + importancia: "alta" + - nombre: "colonia_cluster_id" + tipo: int + importancia: "alta" + - nombre: "distancia_centro_m" + tipo: float + importancia: "media" + - nombre: "distancia_metro_m" + tipo: float + importancia: "media" + - nombre: "distancia_hospital_m" + tipo: float + importancia: "baja" + - nombre: "distancia_escuela_m" + tipo: float + importancia: "media" + - nombre: "indice_seguridad_zona" + tipo: float # 0-100 + importancia: "alta" + - nombre: "nivel_socioeconomico_zona" + tipo: ordinal # A, B, C+, C, D+, D, E + importancia: "alta" + + mercado: + - nombre: "precio_promedio_m2_zona" + tipo: float + importancia: "alta" + - nombre: "tendencia_precios_12m" + tipo: float # % cambio + importancia: "alta" + - nombre: "oferta_activa_zona" + tipo: int + importancia: "media" + - nombre: "absorcion_promedio_zona" + tipo: float # meses + importancia: "alta" + - nombre: "tasa_ocupacion_zona" + tipo: float + importancia: "media" + + derivadas: + - nombre: "precio_m2_comparables" + tipo: float + derivacion: "Promedio ponderado de 5 propiedades similares cercanas" + - nombre: "desviacion_comparables" + tipo: float + derivacion: "Std dev de precios de comparables" + - nombre: "premium_ubicacion" + tipo: float + derivacion: "Factor de ajuste por ubicacion premium" + + algoritmos_candidatos: + - nombre: "XGBoost" + pros: ["Mejor performance general", "Feature importance nativa", "Manejo de missing values"] + contras: ["Requiere tuning", "No extrapola bien"] + recomendado: true + - nombre: "LightGBM" + pros: ["Rapido entrenamiento", "Bajo uso memoria", "Bueno con categoricas"] + contras: ["Sensible a overfitting con pocos datos"] + recomendado: true + - nombre: "CatBoost" + pros: ["Excelente con categoricas", "Menos overfitting"] + contras: ["Mas lento que LightGBM"] + recomendado: false + - nombre: "Neural Network" + pros: ["Puede capturar patrones complejos"] + contras: ["Requiere mucho dato", "Caja negra", "Overengineering para este caso"] + recomendado: false + + metricas_objetivo: + mape: "< 10%" # Mean Absolute Percentage Error + r2_score: ">= 0.85" + rmse: "< 15% del precio medio" + + arquitectura: + ensemble: true + modelos: + - modelo_base: "XGBoost" + peso: 0.5 + - modelo_base: "LightGBM" + peso: 0.3 + - modelo_base: "ElasticNet" # Para regularizacion + peso: 0.2 + + api_endpoints: + - path: "/api/v1/valuation/predict" + method: "POST" + descripcion: "Valuacion de propiedad individual" + request_schema: "PropertyValuationRequest" + response_schema: "PropertyValuationResponse" + - path: "/api/v1/valuation/batch" + method: "POST" + descripcion: "Valuacion de multiples propiedades" + - path: "/api/v1/valuation/explain" + method: "POST" + descripcion: "Valuacion con explicabilidad (SHAP)" + + # -------------------------------------------------------------------------- + # 1.2 Prediccion de Tiempo de Venta (Days on Market) + # -------------------------------------------------------------------------- + prediccion_tiempo_venta: + nombre: "TimeToSellPredictor" + alias: "DOM-Predictor" + tipo: "regression" + descripcion: | + Predice cuantos dias tardara una propiedad en venderse basado en + caracteristicas, precio y condiciones de mercado. + + features: + propiedad: + - nombre: "precio_lista" + tipo: float + importancia: "alta" + - nombre: "precio_vs_mercado_ratio" + tipo: float + importancia: "critica" + descripcion: "Precio lista / Precio promedio zona" + - nombre: "tipo_propiedad" + tipo: categorical + importancia: "alta" + - nombre: "superficie_m2" + tipo: float + importancia: "media" + - nombre: "antiguedad_anos" + tipo: int + importancia: "media" + - nombre: "calidad_fotos" + tipo: ordinal # ML scoring de imagenes + importancia: "alta" + - nombre: "tiene_tour_virtual" + tipo: boolean + importancia: "media" + - nombre: "descripcion_calidad_score" + tipo: float # NLP score + importancia: "media" + + mercado: + - nombre: "inventario_activo_zona" + tipo: int + importancia: "alta" + - nombre: "absorcion_mensual_zona" + tipo: float + importancia: "alta" + - nombre: "estacionalidad_mes" + tipo: int # 1-12 + importancia: "media" + - nombre: "tendencia_demanda_zona" + tipo: float + importancia: "alta" + - nombre: "competencia_precio_similar" + tipo: int + importancia: "alta" + + agente: + - nombre: "experiencia_agente_anos" + tipo: int + importancia: "media" + - nombre: "ventas_previas_agente" + tipo: int + importancia: "media" + - nombre: "calificacion_agente" + tipo: float + importancia: "media" + + algoritmos_candidatos: + - nombre: "Survival Analysis (Cox PH)" + pros: ["Modela censura correctamente", "Interpretable"] + contras: ["Asume proporcionalidad"] + recomendado: true + - nombre: "Random Survival Forest" + pros: ["No asume proporcionalidad", "Captura no-linealidades"] + contras: ["Menos interpretable"] + recomendado: true + - nombre: "XGBoost Regressor" + pros: ["Simple", "Buen performance"] + contras: ["No maneja censura nativamente"] + recomendado: false + + metricas_objetivo: + c_index: ">= 0.75" # Concordance Index + mape: "< 25%" # Dias predichos vs reales + + output: + - dias_estimados: int + - intervalo_confianza: "[min_dias, max_dias]" + - probabilidad_venta_30_dias: float + - probabilidad_venta_60_dias: float + - probabilidad_venta_90_dias: float + + # -------------------------------------------------------------------------- + # 1.3 Prediccion de Demanda por Zona + # -------------------------------------------------------------------------- + prediccion_demanda: + nombre: "ZoneDemandPredictor" + alias: "Demand-Forecaster" + tipo: "time_series_forecasting" + descripcion: | + Pronostico de demanda inmobiliaria por zona geografica + usando series temporales y factores externos. + + features: + historicas: + - nombre: "busquedas_zona_30d" + tipo: int + importancia: "alta" + - nombre: "visitas_propiedades_30d" + tipo: int + importancia: "alta" + - nombre: "contactos_agentes_30d" + tipo: int + importancia: "alta" + - nombre: "ventas_cerradas_30d" + tipo: int + importancia: "critica" + - nombre: "precio_promedio_30d" + tipo: float + importancia: "alta" + + externas: + - nombre: "tasa_interes_hipotecaria" + tipo: float + importancia: "alta" + - nombre: "indice_confianza_consumidor" + tipo: float + importancia: "media" + - nombre: "crecimiento_empleo_zona" + tipo: float + importancia: "media" + - nombre: "proyectos_infraestructura_cerca" + tipo: int + importancia: "alta" + - nombre: "indice_google_trends_zona" + tipo: float + importancia: "media" + + estacionales: + - nombre: "mes_ano" + tipo: int + importancia: "alta" + - nombre: "trimestre" + tipo: int + importancia: "alta" + - nombre: "es_temporada_alta" + tipo: boolean + importancia: "media" + + algoritmos_candidatos: + - nombre: "Prophet (Facebook)" + pros: ["Facil de usar", "Maneja estacionalidad", "Robusto a outliers"] + contras: ["No captura dependencias complejas"] + recomendado: true + - nombre: "LSTM" + pros: ["Captura patrones complejos", "Bueno con secuencias largas"] + contras: ["Requiere mucho dato", "Dificil de tunar"] + recomendado: false + - nombre: "XGBoost + Features temporales" + pros: ["Simple", "Permite features externas facilmente"] + contras: ["No modela dependencia temporal nativamente"] + recomendado: true + - nombre: "ARIMA/SARIMA" + pros: ["Clasico", "Interpretable"] + contras: ["No maneja features externas bien"] + recomendado: false + + metricas_objetivo: + mape: "< 15%" + directional_accuracy: ">= 70%" + + horizontes_prediccion: + - 7_dias + - 30_dias + - 90_dias + - 180_dias + +# ============================================================================ +# 2. ANALISIS DE OPORTUNIDADES +# ============================================================================ + +analisis_oportunidades: + + # -------------------------------------------------------------------------- + # 2.1 Deteccion de Propiedades Subvaluadas + # -------------------------------------------------------------------------- + deteccion_subvaluadas: + nombre: "UndervaluedPropertyDetector" + alias: "Deal-Finder" + tipo: "anomaly_detection" + descripcion: | + Identifica propiedades cuyo precio de lista esta significativamente + por debajo del valor de mercado estimado. + + metodologia: + paso_1: "Calcular valor de mercado con AVM" + paso_2: "Comparar precio_lista vs valor_mercado" + paso_3: "Calcular z-score de la diferencia" + paso_4: "Filtrar donde (valor_mercado - precio_lista) / valor_mercado > umbral" + paso_5: "Validar con comparables recientes" + + umbrales: + oportunidad_moderada: ">= 10%" # Descuento sobre valor mercado + oportunidad_alta: ">= 15%" + oportunidad_excepcional: ">= 20%" + + filtros_calidad: + - "Propiedad activa (no vendida)" + - "Antigedad publicacion < 30 dias" + - "Confianza AVM > 0.75" + - "No es foreclosure o venta forzada" + + output_schema: + propiedad_id: string + precio_lista: float + valor_mercado_estimado: float + descuento_pct: float + nivel_oportunidad: "moderada | alta | excepcional" + confianza_estimacion: float + factores_descuento: + - factor: string + impacto: float + comparables_cercanos: list + + alertas: + - tipo: "nueva_oportunidad" + trigger: "Nueva propiedad con descuento >= 15%" + canales: ["email", "push", "dashboard"] + - tipo: "cambio_precio" + trigger: "Propiedad existente baja precio >10%" + canales: ["email", "push"] + + # -------------------------------------------------------------------------- + # 2.2 Identificacion de Zonas Emergentes + # -------------------------------------------------------------------------- + zonas_emergentes: + nombre: "EmergingZoneIdentifier" + alias: "Zone-Spotter" + tipo: "clustering + trend_analysis" + descripcion: | + Identifica zonas geograficas con potencial de apreciacion + basado en senales tempranas de desarrollo y demanda. + + senales_tempranas: + infraestructura: + - "Nuevas lineas de metro/transporte" + - "Nuevos centros comerciales" + - "Nuevos hospitales/escuelas" + - "Mejoras viales" + peso: 0.25 + + desarrollo: + - "Permisos de construccion nuevos" + - "Proyectos residenciales anunciados" + - "Inversion inmobiliaria en zona" + peso: 0.20 + + demanda: + - "Incremento en busquedas >20% YoY" + - "Reduccion tiempo en mercado" + - "Incremento en precio m2 >10% YoY" + peso: 0.30 + + demograficos: + - "Crecimiento poblacional" + - "Mejora en nivel socioeconomico" + - "Reduccion tasa criminalidad" + peso: 0.15 + + sociales: + - "Nuevos restaurantes/cafes" + - "Apertura coworkings" + - "Eventos culturales" + peso: 0.10 + + algoritmo: + nombre: "Composite Score + Clustering" + pasos: + 1: "Calcular score de cada senal normalizado 0-100" + 2: "Weighted average de todas las senales" + 3: "K-Means clustering de zonas por score" + 4: "Identificar cluster con mejor momentum" + 5: "Ranking dentro del cluster" + + output: + zona_id: string + nombre_zona: string + score_emergente: float # 0-100 + clasificacion: "early_stage | growing | maturing | saturated" + apreciacion_proyectada_12m: float # % + confianza: float + senales_activas: list + mapa_calor: geojson + + # -------------------------------------------------------------------------- + # 2.3 Analisis de ROI Potencial + # -------------------------------------------------------------------------- + analisis_roi: + nombre: "ROIAnalyzer" + alias: "Investment-Analyzer" + tipo: "financial_modeling + ml" + descripcion: | + Calcula el retorno de inversion proyectado para propiedades + considerando diferentes estrategias (renta, flip, desarrollo). + + estrategias: + buy_and_hold: + descripcion: "Comprar y rentar a largo plazo" + metricas: + - cap_rate + - cash_on_cash_return + - noi + - dscr + + flip: + descripcion: "Comprar, remodelar y vender" + metricas: + - roi_bruto + - roi_neto + - marge_ganancia + - tiempo_proyecto + + desarrollo: + descripcion: "Comprar terreno y construir" + metricas: + - irr + - npv + - payback_period + - profit_margin + + inputs_requeridos: + propiedad: + - precio_compra + - superficie_m2 + - estado_actual + - ubicacion + + financiamiento: + - enganche_pct + - tasa_interes + - plazo_anos + + operacion: + - renta_estimada_mensual # ML prediction + - gastos_mantenimiento_pct + - tasa_vacancia + - impuestos_anuales + + mercado: + - apreciacion_proyectada_anual + - inflacion_proyectada + + ml_components: + prediccion_renta: + modelo: "RentPredictor" + tipo: "regression" + features: ["tipo", "ubicacion", "superficie", "amenidades"] + metricas_objetivo: + mape: "< 12%" + + prediccion_apreciacion: + modelo: "AppreciationForecaster" + tipo: "time_series" + features: ["zona", "historico_precios", "factores_externos"] + metricas_objetivo: + mape: "< 15%" + + prediccion_vacancia: + modelo: "VacancyPredictor" + tipo: "classification" + features: ["tipo", "ubicacion", "precio_renta", "competencia"] + metricas_objetivo: + accuracy: ">= 0.80" + + output: + estrategia: string + roi_proyectado_1y: float + roi_proyectado_3y: float + roi_proyectado_5y: float + cash_flow_mensual: float + punto_equilibrio_meses: int + riesgo_nivel: "bajo | medio | alto" + sensibilidad_analisis: + - escenario: "optimista" + roi: float + - escenario: "base" + roi: float + - escenario: "pesimista" + roi: float + +# ============================================================================ +# 3. ANALISIS DE MERCADO +# ============================================================================ + +analisis_mercado: + + # -------------------------------------------------------------------------- + # 3.1 Tendencias de Precios por Zona/Colonia + # -------------------------------------------------------------------------- + tendencias_precios: + nombre: "PriceTrendAnalyzer" + alias: "Market-Pulse" + tipo: "time_series_analysis" + descripcion: | + Analiza y visualiza tendencias de precios historicas y proyectadas + por zona geografica, tipo de propiedad y segmento. + + agregaciones: + temporal: + - diario + - semanal + - mensual + - trimestral + - anual + + geografica: + - pais + - estado + - ciudad + - zona + - colonia + - codigo_postal + + propiedad: + - tipo_propiedad + - rango_precio + - superficie_rango + - antiguedad_rango + + metricas_calculadas: + - precio_promedio_m2 + - precio_mediana_m2 + - precio_percentil_25 + - precio_percentil_75 + - variacion_mensual_pct + - variacion_anual_pct + - volatilidad_30d + - tendencia: "alcista | bajista | lateral" + - fuerza_tendencia: float # 0-100 + + visualizaciones: + - tipo: "line_chart" + uso: "Evolucion temporal de precios" + - tipo: "heatmap" + uso: "Comparativa geografica" + - tipo: "box_plot" + uso: "Distribucion de precios" + - tipo: "scatter_geo" + uso: "Mapa de precios por ubicacion" + + # -------------------------------------------------------------------------- + # 3.2 Analisis Comparativo de Propiedades (Comps) + # -------------------------------------------------------------------------- + analisis_comparativo: + nombre: "PropertyComparator" + alias: "Comp-Analyzer" + tipo: "similarity_search + statistics" + descripcion: | + Encuentra y analiza propiedades comparables para valuacion + y posicionamiento de mercado. + + criterios_similitud: + geograficos: + - radio_km: [0.5, 1, 2, 5] + - misma_colonia: boolean + - mismo_codigo_postal: boolean + + fisicos: + - tipo_propiedad: "exacto" + - superficie_m2: "+/- 20%" + - recamaras: "+/- 1" + - banos: "+/- 1" + - antiguedad: "+/- 10 anos" + + temporales: + - vendidas_ultimos_meses: [3, 6, 12] + - activas_actualmente: boolean + + algoritmo_similitud: + tipo: "weighted_euclidean_distance" + features_numericas: + - superficie_m2 + - num_recamaras + - num_banos + - antiguedad + - latitud + - longitud + features_categoricas: + - tipo_propiedad + - estado_conservacion + normalizacion: "min-max" + pesos: + superficie_m2: 0.25 + ubicacion: 0.30 + tipo: 0.20 + caracteristicas: 0.15 + antiguedad: 0.10 + + output: + propiedad_objetivo: object + comparables: + - propiedad: object + similitud_score: float + distancia_km: float + diferencia_precio_pct: float + fecha_venta: date + dias_en_mercado: int + estadisticas: + precio_promedio_comps: float + precio_mediana_comps: float + precio_min: float + precio_max: float + precio_m2_promedio: float + recomendacion_precio: + valor_sugerido: float + rango_min: float + rango_max: float + confianza: float + + # -------------------------------------------------------------------------- + # 3.3 Indices de Mercado + # -------------------------------------------------------------------------- + indices_mercado: + nombre: "MarketIndexCalculator" + alias: "Index-Engine" + tipo: "statistical_aggregation" + descripcion: | + Calcula indices estandarizados para medir salud y dinamica + del mercado inmobiliario. + + indices: + indice_precios: + nombre: "IPV (Indice de Precios de Vivienda)" + metodologia: "Case-Shiller repeat-sales" + base: 100 + fecha_base: "2020-01-01" + frecuencia: "mensual" + + indice_accesibilidad: + nombre: "IAV (Indice de Accesibilidad a Vivienda)" + formula: "(Ingreso_medio_hogar * 12) / Precio_promedio_vivienda" + interpretacion: | + < 3: Muy accesible + 3-5: Accesible + 5-7: Moderadamente accesible + > 7: Poco accesible + + indice_absorcion: + nombre: "Meses de Inventario" + formula: "Inventario_activo / Ventas_mensuales_promedio" + interpretacion: | + < 4: Mercado de vendedores (alta demanda) + 4-6: Mercado equilibrado + > 6: Mercado de compradores (baja demanda) + + indice_actividad: + nombre: "IAM (Indice de Actividad de Mercado)" + componentes: + - nuevos_listings: peso 0.25 + - ventas_cerradas: peso 0.30 + - visitas_propiedades: peso 0.20 + - dias_en_mercado_inverso: peso 0.25 + escala: 0-100 + + indice_inversion: + nombre: "IRI (Indice de Rendimiento de Inversion)" + componentes: + - cap_rate_promedio: peso 0.35 + - apreciacion_anual: peso 0.35 + - liquidez_mercado: peso 0.15 + - riesgo_inverso: peso 0.15 + escala: 0-100 + + dashboards: + executive_summary: + - indice_precios_trend + - indice_accesibilidad_actual + - indice_absorcion_actual + - mapa_calor_actividad + investor_view: + - indice_inversion_por_zona + - top_zonas_roi + - alertas_oportunidad + agent_view: + - indice_actividad_zona + - tendencia_precios_local + - competencia_analisis + +# ============================================================================ +# 4. REPORTES PROFESIONALES +# ============================================================================ + +reportes_profesionales: + + # -------------------------------------------------------------------------- + # 4.1 Reportes para Agentes Inmobiliarios + # -------------------------------------------------------------------------- + reportes_agentes: + tipos: + cma_report: + nombre: "Comparative Market Analysis (CMA)" + descripcion: "Reporte de valuacion para clientes vendedores" + secciones: + - resumen_ejecutivo + - informacion_propiedad + - analisis_comparables + - ajustes_precio + - precio_recomendado + - condiciones_mercado + - estrategia_venta + formato: ["PDF", "HTML", "PowerPoint"] + personalizacion: + - logo_inmobiliaria + - datos_agente + - branding + + buyer_presentation: + nombre: "Buyer Property Report" + descripcion: "Reporte de propiedad para compradores" + secciones: + - resumen_propiedad + - analisis_precio + - comparativo_mercado + - proyeccion_apreciacion + - analisis_vecindario + - pros_contras + - recomendacion + + market_snapshot: + nombre: "Market Snapshot" + descripcion: "Resumen rapido de mercado local" + secciones: + - indicadores_clave + - tendencia_precios + - inventario_activo + - ventas_recientes + - prediccion_corto_plazo + frecuencia: "semanal" + + listing_performance: + nombre: "Listing Performance Report" + descripcion: "Performance de propiedades activas del agente" + secciones: + - listings_activos + - vistas_por_propiedad + - contactos_recibidos + - comparativa_mercado + - recomendaciones_optimizacion + + # -------------------------------------------------------------------------- + # 4.2 Reportes para Inversores + # -------------------------------------------------------------------------- + reportes_inversores: + tipos: + investment_analysis: + nombre: "Investment Analysis Report" + descripcion: "Analisis completo de inversion inmobiliaria" + secciones: + - resumen_ejecutivo + - descripcion_propiedad + - analisis_mercado_local + - proyecciones_financieras: + - flujo_caja_5_anos + - escenarios_sensibilidad + - roi_proyectado + - cap_rate + - cash_on_cash + - analisis_riesgo + - comparativa_alternativas + - recomendacion + + portfolio_report: + nombre: "Portfolio Performance Report" + descripcion: "Desempeno de portafolio de propiedades" + secciones: + - resumen_portafolio + - valor_actual_total + - ingresos_por_renta + - gastos_operativos + - roi_por_propiedad + - diversificacion_geografica + - diversificacion_tipo + - benchmark_mercado + - rebalanceo_sugerido + + opportunity_alert: + nombre: "Investment Opportunity Alert" + descripcion: "Alerta de oportunidad de inversion" + secciones: + - propiedad_destacada + - por_que_oportunidad + - analisis_rapido + - comparables + - accion_sugerida + - tiempo_limite + + market_outlook: + nombre: "Market Outlook Report" + descripcion: "Perspectivas de mercado a futuro" + secciones: + - tendencias_macro + - prediccion_precios + - zonas_emergentes + - riesgos_identificados + - oportunidades + - recomendaciones_estrategicas + frecuencia: "trimestral" + + # -------------------------------------------------------------------------- + # 4.3 Reportes para Desarrolladores + # -------------------------------------------------------------------------- + reportes_desarrolladores: + tipos: + feasibility_study: + nombre: "Feasibility Study Report" + descripcion: "Estudio de factibilidad para desarrollo" + secciones: + - resumen_ejecutivo + - analisis_sitio: + - ubicacion + - uso_suelo + - topografia + - servicios + - analisis_mercado: + - demanda_zona + - competencia + - precios_venta + - absorcion_proyectos_similares + - analisis_financiero: + - costo_terreno + - costo_construccion + - costo_financiero + - precio_venta_proyectado + - utilidad_proyectada + - irr + - payback + - analisis_riesgo + - recomendacion_go_no_go + + demand_study: + nombre: "Demand Analysis Report" + descripcion: "Estudio de demanda para nuevo desarrollo" + secciones: + - perfil_demografico_zona + - segmento_objetivo + - tamano_mercado + - competencia_directa + - absorcion_proyectada + - precio_optimo + - product_mix_recomendado + + construction_benchmark: + nombre: "Construction Cost Benchmark" + descripcion: "Benchmark de costos de construccion" + secciones: + - costo_m2_por_tipo + - comparativa_zonas + - tendencia_costos + - proveedores_recomendados + - optimizacion_sugerida + + project_tracking: + nombre: "Project Tracking Report" + descripcion: "Seguimiento de proyecto de desarrollo" + secciones: + - avance_construccion + - avance_ventas + - flujo_caja_actual + - desviaciones_vs_plan + - proyeccion_cierre + - alertas + +# ============================================================================ +# 5. TECNICAS ML APLICABLES +# ============================================================================ + +tecnicas_ml: + + # -------------------------------------------------------------------------- + # 5.1 Regresion para Valuacion + # -------------------------------------------------------------------------- + regresion_valuacion: + descripcion: "Modelos de regresion para predecir valores continuos" + + modelos_recomendados: + gradient_boosting: + - nombre: "XGBoost" + uso: "Valuacion de propiedades, prediccion de precios" + hiperparametros: + n_estimators: [100, 500, 1000] + max_depth: [3, 5, 7, 10] + learning_rate: [0.01, 0.05, 0.1] + min_child_weight: [1, 3, 5] + subsample: [0.7, 0.8, 0.9] + colsample_bytree: [0.7, 0.8, 0.9] + pros: + - "Excelente performance en datos tabulares" + - "Feature importance nativa" + - "Maneja missing values" + - "Regularizacion incluida" + contras: + - "Puede overfittear con pocos datos" + - "No extrapola bien" + + - nombre: "LightGBM" + uso: "Datasets grandes, entrenamiento rapido" + hiperparametros: + num_leaves: [31, 50, 100] + max_depth: [-1, 10, 20] + learning_rate: [0.01, 0.05, 0.1] + n_estimators: [100, 500, 1000] + min_child_samples: [20, 50, 100] + pros: + - "Muy rapido" + - "Bajo uso de memoria" + - "Excelente con categoricas" + contras: + - "Sensible a overfitting con pocos datos" + + linear_models: + - nombre: "ElasticNet" + uso: "Baseline, interpretabilidad, regularizacion" + hiperparametros: + alpha: [0.1, 0.5, 1.0] + l1_ratio: [0.1, 0.5, 0.9] + pros: + - "Muy interpretable" + - "Rapido" + - "Buena regularizacion" + contras: + - "No captura no-linealidades" + + feature_engineering: + transformaciones: + - log_transform: "Para precios (distribucion log-normal)" + - sqrt_transform: "Para superficies" + - one_hot_encoding: "Para categoricas con pocas clases" + - target_encoding: "Para categoricas con muchas clases" + - geohash: "Para coordenadas geograficas" + + features_derivadas: + - precio_m2: "precio / superficie" + - ratio_banos_recamaras: "banos / recamaras" + - distancia_amenidades: "Distancia ponderada a servicios" + - score_ubicacion: "Composite score de ubicacion" + + # -------------------------------------------------------------------------- + # 5.2 Clustering para Segmentacion + # -------------------------------------------------------------------------- + clustering_segmentacion: + descripcion: "Agrupacion de propiedades, zonas o clientes similares" + + aplicaciones: + segmentacion_propiedades: + objetivo: "Agrupar propiedades similares para analisis de mercado" + features: + - precio_m2 + - superficie + - tipo + - antiguedad + - ubicacion (lat/lon) + algoritmo_recomendado: "K-Means" + + segmentacion_zonas: + objetivo: "Identificar clusters geograficos con caracteristicas similares" + features: + - precio_promedio_m2 + - nivel_socioeconomico + - densidad_poblacional + - amenidades_cercanas + - crecimiento_historico + algoritmo_recomendado: "DBSCAN" + + segmentacion_compradores: + objetivo: "Perfilar tipos de compradores para targeting" + features: + - presupuesto + - tipo_propiedad_buscada + - zona_preferida + - financiamiento + - urgencia + algoritmo_recomendado: "K-Means + PCA" + + algoritmos: + kmeans: + uso: "Clusters esfericos, numero conocido de clusters" + hiperparametros: + n_clusters: "Determinar con Elbow method o Silhouette" + init: "k-means++" + max_iter: 300 + evaluacion: + - silhouette_score + - calinski_harabasz_score + - davies_bouldin_score + + dbscan: + uso: "Clusters de forma arbitraria, detectar outliers" + hiperparametros: + eps: "Distancia maxima entre puntos" + min_samples: "Minimo puntos para formar cluster" + evaluacion: + - silhouette_score + - noise_ratio + + hierarchical: + uso: "Jerarquia de clusters, dendrograma" + hiperparametros: + linkage: ["ward", "complete", "average"] + n_clusters: "O cortar dendrograma" + evaluacion: + - cophenetic_correlation + - silhouette_score + + # -------------------------------------------------------------------------- + # 5.3 Time Series para Tendencias + # -------------------------------------------------------------------------- + time_series_tendencias: + descripcion: "Analisis y prediccion de series temporales de mercado" + + aplicaciones: + prediccion_precios: + horizonte: "1-12 meses" + granularidad: "mensual" + + prediccion_demanda: + horizonte: "1-6 meses" + granularidad: "semanal o mensual" + + deteccion_estacionalidad: + objetivo: "Identificar patrones estacionales en mercado" + periodos: ["semanal", "mensual", "anual"] + + algoritmos: + prophet: + uso: "Series con estacionalidad multiple, tendencias no lineales" + pros: + - "Facil de usar" + - "Maneja holidays y eventos" + - "Robusto a datos faltantes" + - "Intervalos de confianza automaticos" + contras: + - "No muy preciso para series muy irregulares" + hiperparametros: + changepoint_prior_scale: [0.001, 0.01, 0.1, 0.5] + seasonality_prior_scale: [0.01, 0.1, 1, 10] + seasonality_mode: ["additive", "multiplicative"] + + sarima: + uso: "Series estacionarias con estacionalidad" + pros: + - "Teoricamente solido" + - "Bueno para series cortas" + contras: + - "Asume estacionariedad" + - "Dificil de tunar" + hiperparametros: + p: [0, 1, 2] + d: [0, 1] + q: [0, 1, 2] + P: [0, 1] + D: [0, 1] + Q: [0, 1] + s: 12 # Para mensual + + xgboost_temporal: + uso: "Series con features externas importantes" + pros: + - "Incorpora features externas facilmente" + - "Captura no-linealidades" + contras: + - "No modela dependencia temporal nativamente" + features_temporales: + - lag_features: [1, 7, 30, 365] + - rolling_mean: [7, 30, 90] + - rolling_std: [7, 30] + - day_of_week + - month + - quarter + - is_holiday + + # -------------------------------------------------------------------------- + # 5.4 NLP para Analisis de Descripciones + # -------------------------------------------------------------------------- + nlp_descripciones: + descripcion: | + Procesamiento de lenguaje natural para extraer informacion + y features de descripciones de propiedades. + + aplicaciones: + extraccion_amenidades: + objetivo: "Identificar amenidades mencionadas en descripcion" + tecnica: "Named Entity Recognition (NER) customizado" + entidades: + - "amenidad_interior": ["alberca", "gimnasio", "salon_eventos"] + - "amenidad_exterior": ["jardin", "terraza", "roof_garden"] + - "caracteristica": ["remodelado", "estrenar", "amueblado"] + - "ubicacion_ref": ["cerca_metro", "zona_exclusiva"] + + sentiment_analysis: + objetivo: "Evaluar tono y calidad de descripcion" + metricas: + - positivity_score + - urgency_score + - professionalism_score + + quality_scoring: + objetivo: "Puntuar calidad de la descripcion" + features: + - longitud_palabras + - num_amenidades_mencionadas + - estructura_gramatical + - keywords_relevantes + - llamados_accion + output: "quality_score (0-100)" + + similarity_search: + objetivo: "Encontrar propiedades con descripciones similares" + tecnica: "Embeddings + Cosine Similarity" + + modelos: + embeddings: + - nombre: "text-embedding-3-small" + provider: "OpenAI" + dimensiones: 1536 + uso: "Similarity search, semantic search" + costo: "$0.00002 / 1K tokens" + + - nombre: "all-MiniLM-L6-v2" + provider: "Sentence Transformers" + dimensiones: 384 + uso: "On-premise, bajo costo" + costo: "Gratis (self-hosted)" + + ner: + - nombre: "SpaCy + Custom NER" + uso: "Extraccion de entidades inmobiliarias" + training: "Fine-tune con datos anotados" + + - nombre: "GPT-4 / Claude" + uso: "Zero-shot extraction" + prompt: | + Extrae las siguientes entidades de esta descripcion de propiedad: + - Amenidades interiores + - Amenidades exteriores + - Caracteristicas especiales + - Referencias de ubicacion + + Descripcion: {descripcion} + + Responde en JSON. + + sentiment: + - nombre: "VADER" + uso: "Sentiment basico, rapido" + + - nombre: "RoBERTa fine-tuned" + uso: "Sentiment mas preciso" + +# ============================================================================ +# 6. ARQUITECTURA TECNICA +# ============================================================================ + +arquitectura_tecnica: + + stack_tecnologico: + ml_framework: + - scikit-learn: "Modelos clasicos, preprocessing" + - xgboost: "Gradient boosting" + - lightgbm: "Gradient boosting alternativo" + - pytorch: "Deep learning (si necesario)" + - prophet: "Time series" + + data_processing: + - pandas: "Manipulacion de datos" + - polars: "Procesamiento rapido de grandes datasets" + - numpy: "Operaciones numericas" + - geopandas: "Datos geograficos" + + nlp: + - spacy: "NER, preprocessing" + - sentence-transformers: "Embeddings" + - langchain: "LLM integration" + + api: + - fastapi: "API REST" + - pydantic: "Validacion de schemas" + - uvicorn: "ASGI server" + + mlops: + - mlflow: "Experiment tracking" + - dvc: "Data versioning" + - docker: "Containerization" + + database: + - postgresql: "Datos transaccionales" + - pgvector: "Vector embeddings" + - redis: "Cache de predicciones" + + estructura_proyecto: + ml_service: + - src/: + - api/: + - main.py + - routes/ + - schemas/ + - models/: + - property_valuation/ + - time_to_sell/ + - demand_forecast/ + - opportunity_detector/ + - pipelines/: + - preprocessing.py + - feature_engineering.py + - training/: + - train.py + - evaluate.py + - hyperparameter_tuning.py + - inference/: + - predictor.py + - batch_predictor.py + - nlp/: + - embeddings.py + - entity_extraction.py + - utils/: + - logging.py + - metrics.py + - geo.py + - notebooks/ + - data/: + - raw/ + - processed/ + - models/ + - tests/ + - configs/ + - mlflow/ + - Dockerfile + - requirements.txt + - MODEL_CARDS/ + + api_design: + base_url: "/api/v1/ml" + endpoints: + valuation: + - "POST /valuation/predict" + - "POST /valuation/batch" + - "POST /valuation/explain" + - "GET /valuation/comparables/{property_id}" + + predictions: + - "POST /predictions/time-to-sell" + - "POST /predictions/demand/{zone_id}" + - "GET /predictions/trends/{zone_id}" + + opportunities: + - "GET /opportunities/undervalued" + - "GET /opportunities/emerging-zones" + - "POST /opportunities/roi-analysis" + + market: + - "GET /market/indices" + - "GET /market/trends/{zone_id}" + - "GET /market/comparables/{property_id}" + + reports: + - "POST /reports/cma" + - "POST /reports/investment-analysis" + - "POST /reports/market-snapshot" + + monitoring: + metricas: + modelo: + - prediction_latency_ms + - prediction_count + - error_rate + - drift_score + + negocio: + - accuracy_vs_actual: "Prediccion vs precio real de venta" + - user_adoption: "% de usuarios que usan predicciones" + - report_generation_count + + alertas: + - "Drift detectado > umbral" + - "Latencia > 500ms" + - "Error rate > 5%" + - "Modelo accuracy < 80%" + +# ============================================================================ +# 7. ROADMAP DE IMPLEMENTACION +# ============================================================================ + +roadmap: + fase_1_mvp: + duracion: "4-6 semanas" + entregables: + - "AVM basico (XGBoost)" + - "API de valuacion" + - "Dashboard simple de tendencias" + - "Reporte CMA basico" + prioridad: "CRITICA" + + fase_2_predicciones: + duracion: "4-6 semanas" + entregables: + - "Prediccion tiempo de venta" + - "Prediccion demanda por zona" + - "Detector propiedades subvaluadas" + - "Alertas de oportunidades" + prioridad: "ALTA" + + fase_3_analisis_avanzado: + duracion: "4-6 semanas" + entregables: + - "Indices de mercado" + - "Analisis ROI" + - "Zonas emergentes" + - "Reportes para inversores" + prioridad: "MEDIA" + + fase_4_nlp_optimization: + duracion: "4-6 semanas" + entregables: + - "Analisis NLP descripciones" + - "Scoring calidad listings" + - "Recomendaciones personalizadas" + - "A/B testing de modelos" + prioridad: "MEDIA" + + fase_5_enterprise: + duracion: "4-8 semanas" + entregables: + - "Multi-tenant isolation" + - "Custom models por cliente" + - "API avanzada (batch, webhooks)" + - "Reportes white-label" + prioridad: "BAJA" + +# ============================================================================ +# 8. METRICAS DE EXITO +# ============================================================================ + +metricas_exito: + + modelo: + avm_accuracy: + metrica: "MAPE" + objetivo: "< 10%" + medicion: "Prediccion vs precio de venta real" + + time_to_sell_accuracy: + metrica: "MAPE" + objetivo: "< 25%" + medicion: "Dias predichos vs dias reales" + + demand_forecast: + metrica: "Directional Accuracy" + objetivo: ">= 70%" + medicion: "Prediccion correcta de subida/bajada" + + negocio: + user_adoption: + objetivo: "70% de usuarios activos usan ML features" + medicion: "MAU ML / MAU total" + + report_generation: + objetivo: ">100 reportes/mes por cliente enterprise" + + opportunity_conversion: + objetivo: "30% de oportunidades alertadas son investigadas" + + nps_ml_features: + objetivo: ">= 40" + medicion: "NPS especifico de features ML" + + tecnico: + api_latency: + objetivo: "p95 < 500ms para valuacion individual" + + availability: + objetivo: "99.5% uptime" + + model_freshness: + objetivo: "Re-entrenamiento mensual" diff --git a/shared/knowledge-base/projects/trading-platform/README.md b/shared/knowledge-base/projects/trading-platform/README.md index 34ff338f7..a61348b41 100644 --- a/shared/knowledge-base/projects/trading-platform/README.md +++ b/shared/knowledge-base/projects/trading-platform/README.md @@ -45,9 +45,9 @@ Los siguientes patrones fueron extraidos de trading-platform al catalogo: | Patron | Ubicacion Catalogo | |--------|-------------------| -| Payments (Stripe) | `core/catalog/payments/` | -| WebSocket | `core/catalog/websocket/` | -| Portales/Dashboard | `core/catalog/portales/` | +| Payments (Stripe) | `shared/catalog/payments/` | +| WebSocket | `shared/catalog/websocket/` | +| Portales/Dashboard | `shared/catalog/portales/` | ## Lecciones Aprendidas @@ -58,7 +58,7 @@ Los siguientes patrones fueron extraidos de trading-platform al catalogo: ## Referencias - Proyecto: `projects/trading-platform/` -- Catalogo: `core/catalog/` (modulos extraidos) +- Catalogo: `shared/catalog/` (modulos extraidos) --- diff --git a/shared/knowledge-base/propagacion/NIVELES-PROPAGACION.yml b/shared/knowledge-base/propagacion/NIVELES-PROPAGACION.yml index 29d09f76e..c74758a88 100644 --- a/shared/knowledge-base/propagacion/NIVELES-PROPAGACION.yml +++ b/shared/knowledge-base/propagacion/NIVELES-PROPAGACION.yml @@ -16,7 +16,7 @@ niveles: nivel_0: nombre: "Core Catalog" id: "core-catalog" - ubicacion: "/home/isem/workspace-v1/core/catalog/" + ubicacion: "/home/isem/workspace-v1/shared/catalog/" descripcion: "Modulos de referencia y patrones base" propaga_a: ["nivel_1"] dependencias_internas: false @@ -116,7 +116,7 @@ flujo_cascada: pasos: - paso: 1 nivel: "nivel_0" - accion: "Actualizar modulo en core/catalog" + accion: "Actualizar modulo en shared/catalog" validacion: "Documentacion actualizada" - paso: 2 diff --git a/shared/knowledge-base/propagacion/PROTOCOLO-COORDINACION.yml b/shared/knowledge-base/propagacion/PROTOCOLO-COORDINACION.yml index 8d845b73f..476ecb7e2 100644 --- a/shared/knowledge-base/propagacion/PROTOCOLO-COORDINACION.yml +++ b/shared/knowledge-base/propagacion/PROTOCOLO-COORDINACION.yml @@ -64,21 +64,21 @@ flujo: - paso: 3 nombre: "Actualizacion Level 0 (core)" actor: "@PERFIL_KB_MANAGER" - accion: "Actualizar modulo en core/catalog si aplica" + accion: "Actualizar modulo en shared/catalog si aplica" artefactos_entrada: - "ANALISIS-PROP-XXX.md" - "Codigo de mejora" artefactos_salida: - - "Commit en core/catalog" + - "Commit en shared/catalog" validacion: "Documentacion del modulo actualizada" - condicional: "Solo si mejora afecta core/catalog" + condicional: "Solo si mejora afecta shared/catalog" - paso: 4 nombre: "Actualizacion Level 1 (KB)" actor: "@PERFIL_KB_MANAGER" accion: "Sincronizar con shared/knowledge-base" artefactos_entrada: - - "Cambios en core/catalog" + - "Cambios en shared/catalog" artefactos_salida: - "CATALOGO-MODULOS.yml actualizado" - "Commit en knowledge-base" diff --git a/shared/knowledge-base/propagacion/USAGE-ORQUESTACION.md b/shared/knowledge-base/propagacion/USAGE-ORQUESTACION.md index dbae3ccf0..2d16da1ee 100644 --- a/shared/knowledge-base/propagacion/USAGE-ORQUESTACION.md +++ b/shared/knowledge-base/propagacion/USAGE-ORQUESTACION.md @@ -151,7 +151,7 @@ cat ./propagacion-tasks/EPIC-PROP-*.md | 4. Decide: Propagar / No propagar | | | | Si propagar: | -| - Actualiza core/catalog (nivel 0) | +| - Actualiza shared/catalog (nivel 0) | | - Sincroniza knowledge-base (nivel 1) | | - Genera tareas SCRUM | | - Notifica a @PROJECT_AGENT | @@ -192,7 +192,7 @@ cat ./propagacion-tasks/EPIC-PROP-*.md ## Estructura de Niveles ``` -Nivel 0: core/catalog/ +Nivel 0: shared/catalog/ | v (propaga a) Nivel 1: shared/knowledge-base/ diff --git a/shared/knowledge-base/reference/odoo/docs/01-modulos-core/MOD-base.md b/shared/knowledge-base/reference/odoo/docs/01-modulos-core/MOD-base.md new file mode 100644 index 000000000..26393aced --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/01-modulos-core/MOD-base.md @@ -0,0 +1,165 @@ +# Modulo: Base (Kernel de Odoo) + +**Odoo Module:** base +**Version:** 1.3 +**Categoria:** Hidden +**Es Aplicacion:** No (auto_install: True) + +--- + +## 1. Descripcion General + +El modulo **base** es el kernel fundamental de Odoo, requerido para toda instalacion. +Proporciona la infraestructura central del sistema incluyendo: + +- **Gestion de Datos Maestros:** Contactos, empresas, paises, monedas +- **Autenticacion y Seguridad:** Usuarios, grupos, reglas de acceso +- **Automatizacion:** Tareas programadas (cron), secuencias +- **Metamodelo ORM:** Definicion dinamica de modelos y campos + +Es el unico modulo sin dependencias, siendo la base sobre la cual todos los demas modulos se construyen. + +--- + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| (ninguna) | - | base es el kernel, sin dependencias | + +### 2.2 Dependencias Implicitas (uso de modelos) + +| Modelo Externo | Campo | Modulo Origen | +|---------------|-------|---------------| +| (ninguna) | - | base define los modelos fundamentales | + +### 2.3 Modulos que Dependen de base + +Todos los modulos de Odoo dependen directa o indirectamente de base: +- product, sale, purchase, stock, account, hr, crm, project, mail, web, etc. + +--- + +## 3. Modelos Principales + +### 3.1 Datos Maestros + +| Modelo | Descripcion | Archivo Fuente | +|--------|-------------|----------------| +| res.partner | Contactos/Clientes/Proveedores | models/res_partner.py | +| res.company | Empresas y sucursales | models/res_company.py | +| res.country | Paises | models/res_country.py | +| res.country.state | Estados/Provincias | models/res_country.py | +| res.currency | Monedas | models/res_currency.py | +| res.currency.rate | Tasas de cambio | models/res_currency.py | +| res.bank | Bancos | models/res_bank.py | +| res.partner.bank | Cuentas bancarias | models/res_bank.py | +| res.lang | Idiomas | models/res_lang.py | + +### 3.2 Usuarios y Seguridad + +| Modelo | Descripcion | Archivo Fuente | +|--------|-------------|----------------| +| res.users | Usuarios del sistema | models/res_users.py | +| res.groups | Grupos de seguridad | models/res_groups.py | +| res.groups.privilege | Privilegios de grupos | models/res_groups_privilege.py | +| ir.model.access | Derechos de acceso | models/ir_model.py | +| ir.rule | Reglas de registro | models/ir_rule.py | +| res.device | Dispositivos conectados | models/res_device.py | +| res.users.apikeys | Claves API | models/res_users.py | + +### 3.3 Infraestructura ORM + +| Modelo | Descripcion | Archivo Fuente | +|--------|-------------|----------------| +| ir.model | Metamodelo (definicion de modelos) | models/ir_model.py | +| ir.model.fields | Definicion de campos | models/ir_model.py | +| ir.config.parameter | Parametros de configuracion | models/ir_config_parameter.py | +| ir.default | Valores por defecto | models/ir_default.py | + +### 3.4 Automatizacion + +| Modelo | Descripcion | Archivo Fuente | +|--------|-------------|----------------| +| ir.cron | Tareas programadas | models/ir_cron.py | +| ir.sequence | Secuencias numericas | models/ir_sequence.py | +| ir.actions.server | Acciones de servidor | models/ir_actions.py | +| ir.filters | Filtros guardados | models/ir_filters.py | + +### 3.5 Interfaz y Reportes + +| Modelo | Descripcion | Archivo Fuente | +|--------|-------------|----------------| +| ir.ui.view | Vistas | models/ir_ui_view.py | +| ir.ui.menu | Menus | models/ir_ui_menu.py | +| ir.attachment | Adjuntos | models/ir_attachment.py | +| report.paperformat | Formatos de papel | models/report_paperformat.py | + +--- + +## 4. Integraciones con Otros Modulos + +### 4.1 Modelos Extendidos por Otros Modulos + +| Modelo base | Extendido por | Extension | +|-------------|---------------|-----------| +| res.partner | sale, purchase, account | Campos de ventas, compras, contables | +| res.partner | crm | Campos de oportunidades | +| res.partner | hr | Campos de empleados | +| res.company | account | Configuracion contable | +| res.company | sale, purchase | Configuracion comercial | +| res.users | hr | Vinculo con empleados | +| ir.cron | Varios | Tareas programadas especificas | + +### 4.2 Mixins Proporcionados + +| Mixin | Proposito | Usado por | +|-------|-----------|-----------| +| format.address.mixin | Formateo de direcciones por pais | res.partner, res.company | +| format.vat.label.mixin | Etiquetas VAT personalizadas | res.partner, res.company | +| avatar.mixin | Imagenes en multiples tamanos | res.partner | +| properties.base.definition.mixin | Propiedades dinamicas | Varios | + +--- + +## 5. Notas de Implementacion + +### 5.1 Seguridad + +- **Contraseñas:** Cifradas con pbkdf2_sha512 (minimo 600k rounds) +- **Control de acceso:** Tres niveles (usuarios → grupos → reglas de registro) +- **Anti-brute-force:** Cooldown despues de 5 intentos fallidos +- **API Keys:** Soporta autenticacion programatica con expiracion + +### 5.2 Performance + +- Indices en campos frecuentemente buscados (name, email, ref, vat) +- Cache de definiciones de grupos +- Prefetch optimizado para relaciones + +### 5.3 Restricciones Importantes + +- No se puede eliminar el usuario admin +- No se puede cambiar jerarquia de empresas despues de crear +- Los partners con usuarios activos no se pueden archivar +- Los grupos de usuario (group_user, group_portal, group_public) son mutuamente excluyentes + +--- + +## 6. Estadisticas del Modulo + +| Metrica | Valor | +|---------|-------| +| Archivos de modelos | 49 | +| Modelos principales | ~50 | +| Archivos totales | 693 | +| Complejidad | ALTA | + +--- + +**Referencias:** +- Fuente: `odoo/addons/base/` +- Manifest: `odoo/addons/base/__manifest__.py` +- Documentacion oficial: https://www.odoo.com/documentation/18.0/developer/reference/backend/orm.html diff --git a/shared/knowledge-base/reference/odoo/docs/01-modulos-core/MOD-product.md b/shared/knowledge-base/reference/odoo/docs/01-modulos-core/MOD-product.md new file mode 100644 index 000000000..9c213887c --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/01-modulos-core/MOD-product.md @@ -0,0 +1,183 @@ +# Modulo: Products & Pricelists + +**Odoo Module:** product +**Version:** 1.2 +**Categoria:** Sales/Sales +**Es Aplicacion:** No + +--- + +## 1. Descripcion General + +El modulo **product** gestiona el catalogo de productos y listas de precios de Odoo. +Es un modulo fundamental usado por ventas, compras, inventario y contabilidad. + +### Funcionalidades Principales: +- **Productos y Variantes:** Plantillas con variantes basadas en atributos (talla, color) +- **Listas de Precios:** Reglas de precio por cantidad, categoria, cliente +- **Atributos Configurables:** Radio, pills, select, color, checkbox, imagen +- **Informacion de Proveedores:** Precios y plazos por proveedor +- **Productos Combo:** Paquetes con opciones multiples +- **Unidades de Medida:** Multiples UoM por producto + +--- + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| base | Requerido | Sistema base de Odoo | +| mail | Requerido | Notificaciones y actividades | +| uom | Requerido | Unidades de medida | + +### 2.2 Dependencias Implicitas (uso de modelos) + +| Modelo Externo | Campo | Modulo Origen | +|---------------|-------|---------------| +| res.partner | supplierinfo.partner_id | base | +| res.company | product_template.company_id | base | +| res.currency | pricelist.currency_id | base | +| res.country.group | pricelist.country_group_ids | base | +| uom.uom | product_template.uom_id | uom | +| ir.attachment | product.document | base | + +### 2.3 Modulos que Dependen de product + +- sale (Ventas) +- purchase (Compras) +- stock (Inventario) +- account (Contabilidad) +- mrp (Manufactura) +- pos (Punto de Venta) + +--- + +## 3. Modelos Principales + +### 3.1 Catalogo de Productos + +| Modelo | Descripcion | Archivo Fuente | +|--------|-------------|----------------| +| product.template | Plantilla de producto | models/product_template.py | +| product.product | Variantes de producto | models/product_product.py | +| product.category | Categorias jerarquicas | models/product_category.py | +| product.tag | Etiquetas de productos | models/product_tag.py | + +### 3.2 Atributos y Variantes + +| Modelo | Descripcion | Archivo Fuente | +|--------|-------------|----------------| +| product.attribute | Atributos (Color, Talla) | models/product_attribute.py | +| product.attribute.value | Valores de atributos | models/product_attribute.py | +| product.template.attribute.line | Atributos en plantilla | models/product_attribute.py | +| product.template.attribute.value | Valores en plantilla | models/product_attribute.py | + +### 3.3 Precios + +| Modelo | Descripcion | Archivo Fuente | +|--------|-------------|----------------| +| product.pricelist | Listas de precios | models/product_pricelist.py | +| product.pricelist.item | Reglas de precio | models/product_pricelist.py | +| product.supplierinfo | Precios de proveedores | models/product_supplierinfo.py | + +### 3.4 Adicionales + +| Modelo | Descripcion | Archivo Fuente | +|--------|-------------|----------------| +| product.combo | Productos combo | models/product_combo.py | +| product.combo.item | Items de combo | models/product_combo.py | +| product.document | Documentos adjuntos | models/product_document.py | +| product.packaging | Empaques | models/product_packaging.py | + +--- + +## 4. Integraciones con Otros Modulos + +### 4.1 Extensiones por Modulos + +| Modelo product | Extendido por | Campos Agregados | +|----------------|---------------|------------------| +| product.template | sale | invoice_policy, service_type | +| product.template | purchase | purchase_method, purchase_line_warn | +| product.template | stock | type=product, tracking, routes | +| product.template | account | property_account_income/expense | +| product.product | stock | qty_available, virtual_available | + +### 4.2 Uso en Documentos + +| Documento | Modelo Usado | Campo | +|-----------|--------------|-------| +| sale.order.line | product.product | product_id | +| purchase.order.line | product.product | product_id | +| stock.move | product.product | product_id | +| account.move.line | product.product | product_id | + +--- + +## 5. Notas de Implementacion + +### 5.1 Tipos de Producto + +| Tipo | Descripcion | Comportamiento | +|------|-------------|----------------| +| consu | Bienes/Consumibles | Sin gestion de stock detallada | +| service | Servicios | Sin stock | +| combo | Combo | Paquete con opciones | + +**Nota:** El tipo `product` (almacenable) es agregado por el modulo `stock`. + +### 5.2 Creacion de Variantes + +| Modo | create_variant | Comportamiento | +|------|----------------|----------------| +| Instantly | always | Todas las combinaciones al guardar | +| Dynamically | dynamic | Bajo demanda desde ordenes | +| Never | no_variant | Solo configuracion, sin variantes | + +### 5.3 Tipos de Visualizacion de Atributos + +- **radio:** Botones de opcion +- **pills:** Botones tipo pastilla +- **select:** Dropdown +- **color:** Selector de color +- **multi:** Checkbox (solo con no_variant) +- **image:** Selector de imagen + +### 5.4 Performance + +- **Limite de variantes:** Configurable via `product.dynamic_variant_limit` (default: 1000) +- **Indices:** barcode (btree), default_code, combination_indices +- **Cache:** _get_default_uom_id, _get_variant_id_for_combination + +### 5.5 Precios de Lista (Pricelist) + +Niveles de aplicacion (mayor a menor prioridad): +1. `0_product_variant` - Variante especifica +2. `1_product` - Producto (plantilla) +3. `2_product_category` - Categoria +4. `3_global` - Todos los productos + +Metodos de calculo: +- **list_price:** Precio de venta base +- **standard_price:** Costo +- **pricelist:** Otra lista de precios (recursivo) + +--- + +## 6. Estadisticas del Modulo + +| Metrica | Valor | +|---------|-------| +| Archivos de modelos | 26 | +| Modelos principales | 15+ | +| Lineas de codigo estimadas | 5000+ | +| Complejidad | MEDIA-ALTA | + +--- + +**Referencias:** +- Fuente: `addons/product/` +- Manifest: `addons/product/__manifest__.py` +- Documentacion oficial: https://www.odoo.com/documentation/18.0/applications/sales/products.html diff --git a/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-account.md b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-account.md new file mode 100644 index 000000000..14707903a --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-account.md @@ -0,0 +1,141 @@ +# Modulo: Invoicing (Contabilidad) + +**Odoo Module:** account +**Version:** 1.4 +**Categoria:** Accounting/Accounting +**Es Aplicacion:** Si + +--- + +## 1. Descripcion General + +El modulo **account** es el sistema de facturacion y contabilidad de Odoo. +Gestiona asientos contables, facturas, pagos, impuestos y reconciliacion. + +### Funcionalidades Principales: +- **Asientos Contables (account.move):** Diarios con debitos y creditos +- **Facturas y Notas de Credito:** Clientes y proveedores +- **Pagos:** Entradas y salidas con reconciliacion +- **Impuestos:** Calculos flexibles con distribuciones +- **Cuentas Contables:** Plan de cuentas configurable +- **Diarios:** Sale, Purchase, Bank, Cash, General +- **Reconciliacion:** Parcial y completa + +--- + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| base_setup | Requerido | Configuracion base | +| onboarding | Requerido | Sistema de onboarding | +| product | Requerido | Gestion de productos | +| analytic | Requerido | Contabilidad analitica | +| portal | Requerido | Portal de clientes | +| digest | Requerido | Resumenes por correo | + +### 2.2 Dependencias Implicitas + +| Modelo Externo | Campo | Modulo Origen | +|---------------|-------|---------------| +| res.partner | partner_id | base | +| res.company | company_id | base | +| res.currency | currency_id | base | +| product.product | invoice_line_ids.product_id | product | + +--- + +## 3. Modelos Principales + +### 3.1 Contabilidad Core + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| account.move | Asientos/Facturas | account_move.py | +| account.move.line | Lineas de asiento | account_move_line.py | +| account.account | Cuentas contables | account_account.py | +| account.journal | Diarios | account_journal.py | +| account.tax | Impuestos | account_tax.py | +| account.payment | Pagos | account_payment.py | + +### 3.2 Reconciliacion + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| account.partial.reconcile | Reconciliacion parcial | account_partial_reconcile.py | +| account.full.reconcile | Reconciliacion completa | account_full_reconcile.py | +| account.reconcile.model | Modelos de reconciliacion | account_reconcile_model.py | + +### 3.3 Banco + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| account.bank.statement | Extractos bancarios | account_bank_statement.py | +| account.bank.statement.line | Lineas de extracto | account_bank_statement_line.py | + +### 3.4 Configuracion + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| account.payment.term | Terminos de pago | account_payment_term.py | +| account.fiscal.position | Posiciones fiscales | account_fiscal_position.py | +| chart.template | Plantillas de plan | chart_template.py | + +--- + +## 4. Tipos de Movimiento (move_type) + +| Tipo | Descripcion | Diario | +|------|-------------|--------| +| entry | Asiento contable | General | +| out_invoice | Factura cliente | Sale | +| out_refund | Nota credito cliente | Sale | +| in_invoice | Factura proveedor | Purchase | +| in_refund | Nota credito proveedor | Purchase | +| out_receipt | Recibo de venta | Sale | +| in_receipt | Recibo de compra | Purchase | + +--- + +## 5. Tipos de Cuenta (account_type) + +| Categoria | Tipos | +|-----------|-------| +| Asset | receivable, cash, current, non_current, prepayments, fixed | +| Liability | payable, credit_card, current, non_current | +| Equity | equity, unaffected (Current Year Earnings) | +| Income | income, other | +| Expense | expenses, other, depreciation, direct_cost | +| Off Balance | off_balance | + +--- + +## 6. Tipos de Diario (journal type) + +| Tipo | Uso | +|------|-----| +| sale | Facturas de clientes | +| purchase | Facturas de proveedores | +| bank | Cuentas bancarias | +| cash | Caja | +| credit | Tarjetas de credito | +| general | Asientos varios | + +--- + +## 7. Estadisticas del Modulo + +| Metrica | Valor | +|---------|-------| +| Archivos de modelos | 55 | +| Modelos principales | 30+ | +| Lineas de codigo estimadas | 50,000+ | +| Complejidad | ALTA | + +--- + +**Referencias:** +- Fuente: `addons/account/` +- Manifest: `addons/account/__manifest__.py` diff --git a/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-analytic.md b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-analytic.md new file mode 100644 index 000000000..665712dd9 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-analytic.md @@ -0,0 +1,123 @@ +# Modulo: Analytic Accounting (Contabilidad Analitica) + +**Odoo Module:** analytic +**Version:** 1.2 +**Categoria:** Accounting/Accounting +**Es Aplicacion:** No (modulo auxiliar) + +--- + +## 1. Descripcion General + +El modulo **analytic** proporciona contabilidad analitica independiente de la general. +Permite distribuir costos e ingresos entre multiples centros de costo/proyectos. + +### Funcionalidades Principales: +- **Cuentas Analiticas (account.analytic.account):** Centros de costo/proyectos +- **Lineas Analiticas (account.analytic.line):** Movimientos analiticos +- **Planes Analiticos (account.analytic.plan):** Estructura jerarquica de planes +- **Distribuciones (analytic.mixin):** Asignar a multiples cuentas con porcentajes +- **Modelos de Distribucion:** Pre-configurar distribuciones automaticas + +--- + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| base | Requerido | Modulo base | +| mail | Requerido | Mensajeria | +| uom | Requerido | Unidades de medida | + +--- + +## 3. Modelos Principales + +### 3.1 Analytic Core + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| account.analytic.account | Cuentas analiticas | analytic_account.py | +| account.analytic.line | Lineas analiticas | analytic_line.py | +| account.analytic.plan | Planes analiticos | analytic_plan.py | +| account.analytic.applicability | Reglas aplicabilidad | analytic_plan.py | + +### 3.2 Distribucion + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| account.analytic.distribution.model | Modelos distribucion | analytic_distribution_model.py | + +### 3.3 Mixins + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| analytic.mixin | Campos distribucion JSON | analytic_mixin.py | +| analytic.plan.fields.mixin | Campos dinamicos por plan | analytic_line.py | + +--- + +## 4. Integraciones con Otros Modulos + +El modulo analytic se integra ampliamente: + +| Modulo | Modelo | Integracion | +|--------|--------|-------------| +| account | account.move.line | analytic.mixin | +| sale | sale.order.line | analytic.mixin | +| purchase | purchase.order.line | analytic.mixin | +| hr_timesheet | hr.timesheet | analytic.plan.fields.mixin | +| hr_expense | hr.expense | analytic.mixin | +| project | project.project | account_id M2O | +| stock_account | stock.move | ? | +| mrp_account | mrp.production | ? | + +--- + +## 5. Formato de Distribucion JSON + +```json +{ + "account_id1,account_id2": 50.0, + "account_id3": 50.0 +} +``` + +- Claves: IDs de cuentas separados por coma +- Valores: Porcentaje (total debe ser 100% para obligatorios) + +--- + +## 6. Aplicabilidad de Planes + +| Tipo | Descripcion | +|------|-------------| +| optional | Plan opcional | +| mandatory | Plan obligatorio (debe sumar 100%) | +| unavailable | Plan no disponible | + +--- + +## 7. Grupos de Seguridad + +| Grupo | Permiso | +|-------|---------| +| group_analytic_accounting | Acceso contabilidad analitica | + +--- + +## 8. Estadisticas del Modulo + +| Metrica | Valor | +|---------|-------| +| Modelos principales | 5 | +| Mixins | 2 | +| Complejidad | MEDIA | + +--- + +**Referencias:** +- Fuente: `addons/analytic/` +- Manifest: `addons/analytic/__manifest__.py` diff --git a/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-crm.md b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-crm.md new file mode 100644 index 000000000..022dba4f9 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-crm.md @@ -0,0 +1,155 @@ +# Modulo: CRM + +**Odoo Module:** crm +**Version:** 1.9 +**Categoria:** Sales/CRM +**Es Aplicacion:** Si + +--- + +## 1. Descripcion General + +El modulo **crm** gestiona el pipeline de ventas completo de Odoo. +Desde leads hasta oportunidades ganadas/perdidas con scoring predictivo. + +### Funcionalidades Principales: +- **Leads/Opportunities (crm.lead):** Gestion unificada de prospectos +- **Pipeline de Ventas (crm.stage):** Etapas configurables +- **Equipos de Venta (crm.team):** Organizacion comercial +- **Predictive Lead Scoring (PLS):** Probabilidades automaticas +- **Conversion Lead→Opportunity:** Flujo de calificacion +- **Fusion de Oportunidades:** Merge de duplicados + +--- + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| base_setup | Requerido | Configuracion base | +| sales_team | Requerido | Equipos de venta | +| mail | Requerido | Mensajeria | +| calendar | Requerido | Calendario | +| resource | Requerido | Recursos | +| utm | Requerido | Tracking UTM | +| web_tour | Requerido | Tours guiados | +| contacts | Requerido | Contactos | +| digest | Requerido | Resumenes | +| phone_validation | Requerido | Validacion telefonos | + +### 2.2 Dependencias Implicitas + +| Modelo Externo | Campo | Modulo Origen | +|---------------|-------|---------------| +| res.partner | partner_id | base | +| res.company | company_id | base | +| res.users | user_id | base | +| crm.team | team_id | sales_team | +| utm.campaign | campaign_id | utm | + +--- + +## 3. Modelos Principales + +### 3.1 CRM Core + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| crm.lead | Leads/Opportunities | crm_lead.py | +| crm.stage | Etapas pipeline | crm_stage.py | +| crm.team | Extension equipos | crm_team.py | +| crm.team.member | Miembros equipo | crm_team_member.py | + +### 3.2 Configuracion + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| crm.lost.reason | Razones de perdida | crm_lost_reason.py | +| crm.recurring.plan | Planes recurrentes | crm_recurring_plan.py | +| crm.tag | Etiquetas | crm_tag.py (sales_team) | + +### 3.3 Lead Scoring + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| crm.lead.scoring.frequency | Frecuencias Naive Bayes | crm_lead_scoring_frequency.py | + +--- + +## 4. Tipos de Registro (type) + +| Tipo | Descripcion | Uso | +|------|-------------|-----| +| lead | Lead | Pre-calificado, requiere investigacion | +| opportunity | Opportunity | Calificado, en proceso de venta | + +--- + +## 5. Estados de Oportunidad (won_status) + +| Estado | Descripcion | Condicion | +|--------|-------------|-----------| +| won | Ganada | probability=100 Y stage.is_won=True | +| lost | Perdida | active=False Y probability=0 | +| pending | Pendiente | Cualquier otro caso | + +--- + +## 6. Etapas Predeterminadas + +| Etapa | Sequence | Descripcion | +|-------|----------|-------------| +| New | 1 | Nueva lead | +| Qualified | 2 | Calificada | +| Proposition | 3 | Propuesta enviada | +| Won | 70 | Ganada (is_won=True) | + +--- + +## 7. Predictive Lead Scoring (PLS) + +Sistema Naive Bayes para predecir probabilidad de cierre: + +| Componente | Descripcion | +|------------|-------------| +| Variables | country_id, source_id, campaign_id, etc. | +| Frecuencias | won_count, lost_count por valor | +| Calculo | P(Won|A,B) basado en frecuencias | +| Resultado | automated_probability (0-100%) | + +--- + +## 8. Grupos de Seguridad + +| Grupo | Permiso | +|-------|---------| +| sales_team.group_sale_salesman | Vendedor | +| sales_team.group_sale_manager | Gerente ventas | + +--- + +## 9. Wizards + +| Wizard | Funcion | +|--------|---------| +| crm.lead2opportunity.partner | Convertir lead a opportunity | +| crm.lead.lost | Marcar como perdida | + +--- + +## 10. Estadisticas del Modulo + +| Metrica | Valor | +|---------|-------| +| Lineas crm_lead.py | 2,877 | +| Modelos principales | 7 | +| Campos en crm.lead | 80+ | +| Complejidad | ALTA | + +--- + +**Referencias:** +- Fuente: `addons/crm/` +- Manifest: `addons/crm/__manifest__.py` diff --git a/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-hr.md b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-hr.md new file mode 100644 index 000000000..527b90279 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-hr.md @@ -0,0 +1,147 @@ +# Modulo: Employees (Recursos Humanos) + +**Odoo Module:** hr +**Version:** 1.1 +**Categoria:** Human Resources/Employees +**Es Aplicacion:** Si + +--- + +## 1. Descripcion General + +El modulo **hr** es el nucleo de gestion de recursos humanos de Odoo. +Centraliza informacion de empleados, departamentos, puestos y estructura organizacional. + +### Funcionalidades Principales: +- **Empleados (hr.employee):** Gestion completa con sistema de versiones +- **Departamentos (hr.department):** Estructura jerarquica organizacional +- **Puestos de Trabajo (hr.job):** Definicion de cargos y vacantes +- **Ubicaciones (hr.work.location):** Lugares de trabajo +- **Categorias/Tags:** Clasificacion de empleados +- **Tipos de Contrato:** Configuracion de contratos +- **Planes de Actividad:** Onboarding/Offboarding + +--- + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| base_setup | Requerido | Configuracion base | +| digest | Requerido | Resumenes por correo | +| phone_validation | Requerido | Validacion telefonos | +| resource_mail | Requerido | Recursos y correo | +| web | Requerido | Interfaz web | + +### 2.2 Dependencias Implicitas + +| Modelo Externo | Campo | Modulo Origen | +|---------------|-------|---------------| +| res.users | user_id | base | +| res.company | company_id | base | +| res.partner | work_contact_id | base | +| resource.resource | resource_id | resource | + +--- + +## 3. Modelos Principales + +### 3.1 HR Core + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| hr.employee | Empleados | hr_employee.py | +| hr.employee.public | Vista publica empleados | hr_employee_public.py | +| hr.department | Departamentos | hr_department.py | +| hr.job | Puestos de trabajo | hr_job.py | +| hr.version | Versiones de empleado | hr_version.py | + +### 3.2 Configuracion + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| hr.work.location | Ubicaciones de trabajo | hr_work_location.py | +| hr.employee.category | Categorias/Tags | hr_employee_category.py | +| hr.contract.type | Tipos de contrato | hr_contract_type.py | +| hr.departure.reason | Razones de partida | hr_departure_reason.py | +| hr.payroll.structure.type | Tipos estructura salarial | hr_payroll_structure_type.py | + +--- + +## 4. Sistema de Versiones + +El modulo implementa control de versiones para auditar cambios en empleados: + +| Campo | Descripcion | +|-------|-------------| +| version_ids | Historico de versiones | +| current_version_id | Version actual | +| date_version | Fecha efectiva de version | +| is_current/is_past/is_future | Estados de version | + +--- + +## 5. Tipos de Empleado (employee_type) + +| Tipo | Descripcion | +|------|-------------| +| employee | Empleado regular | +| worker | Trabajador | +| student | Estudiante | +| trainee | Aprendiz | +| contractor | Contratista | +| freelancer | Freelancer | + +--- + +## 6. Tipos de Contrato Predeterminados + +| Tipo | Descripcion | +|------|-------------| +| Permanent | Permanente | +| Temporary | Temporal | +| Interim | Interino | +| Seasonal | Estacional | +| Full-Time | Tiempo completo | +| Part-Time | Tiempo parcial | + +--- + +## 7. Grupos de Seguridad + +| Grupo | Permiso | +|-------|---------| +| group_hr_user | Officer: Gestionar empleados | +| group_hr_manager | Administrador HR | + +--- + +## 8. Planes de Actividad + +### Onboarding: +- Setup IT Materials +- Plan Training +- Training + +### Offboarding: +- Organize knowledge transfer +- Take Back HR Materials + +--- + +## 9. Estadisticas del Modulo + +| Metrica | Valor | +|---------|-------| +| Lineas de codigo modelos | 4,135 | +| Modelos principales | 10+ | +| Campos en hr.employee | 100+ | +| Complejidad | MEDIA-ALTA | + +--- + +**Referencias:** +- Fuente: `addons/hr/` +- Manifest: `addons/hr/__manifest__.py` diff --git a/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-project.md b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-project.md new file mode 100644 index 000000000..8971f2bb3 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-project.md @@ -0,0 +1,147 @@ +# Modulo: Project (Proyectos) + +**Odoo Module:** project +**Version:** 1.3 +**Categoria:** Services/Project +**Es Aplicacion:** Si + +--- + +## 1. Descripcion General + +El modulo **project** proporciona gestion completa de proyectos y tareas. +Incluye vistas Kanban, dependencias, recurrencia y milestones. + +### Funcionalidades Principales: +- **Proyectos (project.project):** Contenedores de tareas con configuracion +- **Tareas (project.task):** Unidades de trabajo con subtareas +- **Etapas de Tareas (project.task.type):** Pipeline kanban +- **Milestones (project.milestone):** Hitos de entregas +- **Dependencias:** Bloqueo entre tareas +- **Recurrencia:** Tareas repetitivas +- **Etapas Personales:** Vista por usuario + +--- + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| analytic | Requerido | Contabilidad analitica | +| base_setup | Requerido | Configuracion base | +| mail | Requerido | Mensajeria | +| portal | Requerido | Portal clientes | +| rating | Requerido | Calificaciones | +| resource | Requerido | Recursos | +| web | Requerido | Interfaz web | +| web_tour | Requerido | Tours guiados | +| digest | Requerido | Resumenes | + +--- + +## 3. Modelos Principales + +### 3.1 Project Core + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| project.project | Proyectos | project_project.py | +| project.task | Tareas | project_task.py | +| project.task.type | Etapas tareas | project_task_type.py | +| project.milestone | Hitos | project_milestone.py | + +### 3.2 Configuracion + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| project.project.stage | Etapas proyectos | project_project_stage.py | +| project.task.stage.personal | Etapas personales | project_task_stage_personal.py | +| project.task.recurrence | Recurrencia | project_task_recurrence.py | + +--- + +## 4. Estados de Tarea (state) + +| Estado | Descripcion | Tipo | +|--------|-------------|------| +| 01_in_progress | En Progreso | OPEN | +| 02_changes_requested | Cambios Solicitados | OPEN | +| 03_approved | Aprobada | OPEN | +| 04_waiting_normal | Esperando | OPEN | +| 1_done | Completada | CLOSED | +| 1_canceled | Cancelada | CLOSED | + +--- + +## 5. Caracteristicas Avanzadas + +### 5.1 Subtareas +- Relacion padre-hijo recursiva +- Herencia de tags y milestone +- Conteo de subtareas + +### 5.2 Dependencias +- depend_on_ids: Tareas que bloquean +- dependent_ids: Tareas bloqueadas +- Auto-estado waiting si bloqueada + +### 5.3 Recurrencia +- repeat_interval + repeat_unit +- repeat_type: Forever/Until +- Crea siguiente tarea al cerrar + +### 5.4 Milestones +- Vinculo proyecto-hitos +- Tareas asociadas a hitos +- Tracking de progreso + +### 5.5 Etapas Personales +- Cada usuario tiene su vista +- Sincronizado con tabla personal +- Independiente de etapa global + +--- + +## 6. Grupos de Seguridad + +| Grupo | Permiso | +|-------|---------| +| group_project_user | Usuario de proyecto | +| group_project_manager | Gestor de proyectos | +| group_project_stages | Etapas de proyecto | +| group_project_milestone | Milestones | +| group_project_task_dependencies | Dependencias | +| group_project_recurring_tasks | Tareas recurrentes | + +--- + +## 7. Vistas Disponibles + +| Vista | Descripcion | +|-------|-------------| +| Kanban | Agrupado por stage_id | +| List | Vista tabular | +| Form | Detalle completo | +| Activity | Historial cambios | +| Graph | Analisis grafico | +| Pivot | Analisis multidimensional | +| Calendar | Vista temporal | + +--- + +## 8. Estadisticas del Modulo + +| Metrica | Valor | +|---------|-------| +| Lineas project_task.py | 2,212 | +| Lineas project_project.py | 1,443 | +| Modelos principales | 6 | +| Complejidad | ALTA | + +--- + +**Referencias:** +- Fuente: `addons/project/` +- Manifest: `addons/project/__manifest__.py` diff --git a/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-purchase.md b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-purchase.md new file mode 100644 index 000000000..8e81d94bb --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-purchase.md @@ -0,0 +1,137 @@ +# Modulo: Purchase (Compras) + +**Odoo Module:** purchase +**Version:** 1.2 +**Categoria:** Supply Chain/Purchase +**Es Aplicacion:** Si + +--- + +## 1. Descripcion General + +El modulo **purchase** gestiona el ciclo completo de compras de Odoo. +Desde solicitudes de cotizacion (RFQ) hasta ordenes de compra confirmadas. + +### Funcionalidades Principales: +- **Ordenes de Compra (purchase.order):** Gestion completa del ciclo de compra +- **Solicitudes de Cotizacion (RFQ):** Envio y seguimiento de solicitudes +- **Aprobacion de Compras:** Flujo de aprobacion configurable +- **Integracion con Facturacion:** Vinculo con facturas de proveedor +- **Integracion con Inventario:** Generacion automatica de recepciones +- **Terminos de Pago:** Configuracion por proveedor + +--- + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| account | Requerido | Facturacion y contabilidad | + +### 2.2 Dependencias Implicitas + +| Modelo Externo | Campo | Modulo Origen | +|---------------|-------|---------------| +| res.partner | partner_id | base | +| res.company | company_id | base | +| res.currency | currency_id | base | +| product.product | product_id | product | +| account.move | invoice_ids | account | +| account.fiscal.position | fiscal_position_id | account | +| account.payment.term | payment_term_id | account | + +--- + +## 3. Modelos Principales + +### 3.1 Compras Core + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| purchase.order | Orden de compra | purchase_order.py | +| purchase.order.line | Linea de orden | purchase_order_line.py | +| purchase.bill.line.match | Matching de facturas | purchase_bill_line_match.py | + +### 3.2 Extensiones + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| res.partner | Extension proveedor | res_partner.py | +| res.company | Config empresa | res_company.py | +| product.product | Extension producto | product.py | +| product.template | Extension plantilla | product.py | + +--- + +## 4. Estados de Orden de Compra (state) + +| Estado | Nombre UI | Descripcion | Siguiente | +|--------|-----------|-------------|-----------| +| draft | RFQ | Solicitud de cotizacion | sent, to approve, purchase | +| sent | RFQ Sent | RFQ enviada al proveedor | to approve, purchase | +| to approve | To Approve | Pendiente de aprobacion | purchase, cancel | +| purchase | Purchase Order | Orden confirmada | cancel (solo desbloquear) | +| cancel | Cancelled | Orden cancelada | draft | + +--- + +## 5. Estados de Facturacion (invoice_status) + +| Estado | Descripcion | +|--------|-------------| +| no | Nada que facturar | +| to invoice | Pendiente de facturar | +| invoiced | Completamente facturado | + +--- + +## 6. Flujo de Aprobacion + +| Condicion | Comportamiento | +|-----------|----------------| +| Sin doble validacion | draft → purchase directo | +| Con doble validacion | draft → to approve → purchase | +| Monto sobre limite | Requiere aprobacion gerente | + +--- + +## 7. Integraciones + +### 7.1 Con Inventario (stock) +- Genera albaranes de recepcion automaticamente +- Vincula movimientos con lineas de compra +- Actualiza cantidades recibidas + +### 7.2 Con Contabilidad (account) +- Genera facturas de proveedor +- Reconcilia con ordenes de compra +- Aplica posiciones fiscales + +--- + +## 8. Grupos de Seguridad + +| Grupo | Permiso | +|-------|---------| +| group_purchase_user | Usuario de compras | +| group_purchase_manager | Gerente de compras | +| group_warning_purchase | Ver advertencias | + +--- + +## 9. Estadisticas del Modulo + +| Metrica | Valor | +|---------|-------| +| Archivos de modelos | 8 | +| Modelos principales | 3 | +| Lineas de codigo estimadas | 15,000+ | +| Complejidad | MEDIA | + +--- + +**Referencias:** +- Fuente: `addons/purchase/` +- Manifest: `addons/purchase/__manifest__.py` diff --git a/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-sale.md b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-sale.md new file mode 100644 index 000000000..05f4eafdd --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-sale.md @@ -0,0 +1,160 @@ +# Modulo: Sales (Ventas) + +**Odoo Module:** sale +**Version:** 1.2 +**Categoria:** Sales/Sales +**Es Aplicacion:** No (es modulo base de ventas) + +--- + +## 1. Descripcion General + +El modulo **sale** es el nucleo del sistema de ventas de Odoo. +Gestiona cotizaciones, ordenes de venta, y su integracion con facturacion. + +### Funcionalidades Principales: +- **Ordenes de Venta (sale.order):** Gestion completa del ciclo de venta +- **Cotizaciones:** Creacion y envio a clientes +- **Firma Online:** Requerimiento de firma digital +- **Pago Online:** Integracion con pasarelas de pago +- **Integracion Facturacion:** Generacion automatica de facturas +- **Equipos de Venta:** Asignacion a equipos comerciales + +--- + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| sales_team | Requerido | Equipos de venta | +| account_payment | Requerido | Pagos y portal | +| utm | Requerido | Tracking de marketing | + +### 2.2 Dependencias Implicitas + +| Modelo Externo | Campo | Modulo Origen | +|---------------|-------|---------------| +| res.partner | partner_id | base | +| res.company | company_id | base | +| res.currency | currency_id | base | +| product.product | product_id | product | +| account.move | invoice_ids | account | +| crm.team | team_id | sales_team | +| payment.transaction | transaction_ids | payment | + +--- + +## 3. Modelos Principales + +### 3.1 Ventas Core + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| sale.order | Orden de venta | sale_order.py | +| sale.order.line | Linea de orden | sale_order_line.py | + +### 3.2 Wizards + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| sale.advance.payment.inv | Crear factura | sale_make_invoice_advance.py | +| sale.order.discount | Aplicar descuento | sale_order_discount.py | +| sale.mass.cancel.orders | Cancelacion masiva | mass_cancel_orders.py | + +### 3.3 Extensiones + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| account.move | Extension factura | account_move.py | +| payment.transaction | Transacciones | payment_transaction.py | +| crm.team | Extension equipo | crm_team.py | +| product.template | Extension producto | product_template.py | + +--- + +## 4. Estados de Orden de Venta (state) + +| Estado | Nombre UI | Descripcion | Siguiente | +|--------|-----------|-------------|-----------| +| draft | Quotation | Cotizacion inicial | sent, sale, cancel | +| sent | Quotation Sent | Cotizacion enviada | sale, cancel | +| sale | Sales Order | Orden confirmada | cancel | +| cancel | Cancelled | Orden cancelada | draft | + +--- + +## 5. Estados de Facturacion (invoice_status) + +| Estado | Descripcion | +|--------|-------------| +| upselling | Oportunidad de upselling | +| invoiced | Totalmente facturado | +| to invoice | Pendiente de facturar | +| no | Nada que facturar | + +--- + +## 6. Opciones de Confirmacion + +| Opcion | Descripcion | +|--------|-------------| +| require_signature | Requiere firma online | +| require_payment | Requiere pago online | +| prepayment_percent | Porcentaje de anticipo | + +--- + +## 7. Integraciones + +### 7.1 Con Inventario (sale_stock) +- Genera albaranes de envio +- Reserva stock automaticamente +- Actualiza cantidades entregadas + +### 7.2 Con Contabilidad (account) +- Genera facturas de cliente +- Multiples metodos de facturacion +- Anticipo y factura final + +### 7.3 Con Pagos (payment) +- Pasarelas de pago online +- Links de pago +- Transacciones vinculadas + +--- + +## 8. Grupos de Seguridad + +| Grupo | Permiso | +|-------|---------| +| group_sale_salesman | Vendedor | +| group_sale_salesman_all_leads | Ver todas oportunidades | +| group_sale_manager | Gerente de ventas | +| group_warning_sale | Ver advertencias | + +--- + +## 9. Hooks de Ciclo de Vida + +| Hook | Descripcion | +|------|-------------| +| post_init_hook | _post_init_hook - configuracion inicial | + +--- + +## 10. Estadisticas del Modulo + +| Metrica | Valor | +|---------|-------| +| Archivos de modelos | 15+ | +| Modelos principales | 2 | +| Lineas de codigo estimadas | 25,000+ | +| Complejidad | ALTA | + +--- + +**Referencias:** +- Fuente: `addons/sale/` +- Manifest: `addons/sale/__manifest__.py` diff --git a/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-stock.md b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-stock.md new file mode 100644 index 000000000..f6aef7006 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/02-modulos-business/MOD-stock.md @@ -0,0 +1,156 @@ +# Modulo: Inventory (Inventario) + +**Odoo Module:** stock +**Version:** 1.1 +**Categoria:** Supply Chain/Inventory +**Es Aplicacion:** Si + +--- + +## 1. Descripcion General + +El modulo **stock** es el sistema de gestion de inventario y almacenes de Odoo. +Gestiona movimientos de stock, albaranes, ubicaciones, cantidades y reglas de procura. + +### Funcionalidades Principales: +- **Movimientos de Stock (stock.move):** Transferencia de productos entre ubicaciones +- **Albaranes/Transferencias (stock.picking):** Agrupacion de movimientos +- **Almacenes (stock.warehouse):** Configuracion multi-almacen con flujos personalizables +- **Ubicaciones (stock.location):** Estructura jerarquica de almacenamiento +- **Cantidades (stock.quant):** Registro preciso de inventario +- **Reglas (stock.rule):** Automatizacion de reposicion +- **Lotes/Numeros de Serie (stock.lot):** Trazabilidad de productos +- **Descartes (stock.scrap):** Gestion de mermas + +--- + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| product | Requerido | Gestion de productos | +| barcodes_gs1_nomenclature | Requerido | Nomenclatura codigos de barras | +| digest | Requerido | Resumenes por correo | + +### 2.2 Dependencias Implicitas + +| Modelo Externo | Campo | Modulo Origen | +|---------------|-------|---------------| +| res.partner | partner_id | base | +| res.company | company_id | base | +| res.users | user_id | base | +| product.product | product_id | product | +| uom.uom | product_uom | uom | + +--- + +## 3. Modelos Principales + +### 3.1 Stock Core + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| stock.move | Movimientos de stock | stock_move.py | +| stock.move.line | Lineas de operacion | stock_move_line.py | +| stock.picking | Albaranes/Transferencias | stock_picking.py | +| stock.location | Ubicaciones | stock_location.py | +| stock.warehouse | Almacenes | stock_warehouse.py | +| stock.quant | Cantidades de stock | stock_quant.py | + +### 3.2 Reglas y Automatizacion + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| stock.rule | Reglas de procura | stock_rule.py | +| stock.route | Rutas de almacen | stock_route.py | +| stock.orderpoint | Puntos de reorden | stock_orderpoint.py | +| stock.putaway.rule | Reglas de ubicacion | stock_putaway_rule.py | + +### 3.3 Trazabilidad + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| stock.lot | Lotes/Numeros de serie | stock_lot.py | +| stock.package | Paquetes | stock_package.py | +| stock.scrap | Descartes/Mermas | stock_scrap.py | + +### 3.4 Configuracion + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| stock.picking.type | Tipos de operacion | stock_picking_type.py | +| stock.storage.category | Categorias de almacenamiento | stock_storage_category.py | +| product.removal | Estrategias de extraccion | product_removal.py | + +--- + +## 4. Tipos de Operacion (picking_type_code) + +| Tipo | Descripcion | Uso | +|------|-------------|-----| +| incoming | Recepciones | Entradas de proveedor | +| outgoing | Envios | Salidas a cliente | +| internal | Transferencias internas | Entre ubicaciones | + +--- + +## 5. Tipos de Ubicacion (usage) + +| Tipo | Descripcion | Virtual | +|------|-------------|---------| +| supplier | Proveedores | Si | +| customer | Clientes | Si | +| internal | Interna fisica | No | +| view | Virtual/Jerarquica | Si | +| inventory | Ajustes de inventario | Si | +| production | Produccion | Si | +| transit | Transito | Si | + +--- + +## 6. Pasos de Recepcion (reception_steps) + +| Paso | Descripcion | Ubicaciones | +|------|-------------|-------------| +| one_step | Recibir y almacenar | Stock | +| two_steps | Recibir, luego almacenar | Input → Stock | +| three_steps | Recibir, QC, almacenar | Input → QC → Stock | + +--- + +## 7. Pasos de Entrega (delivery_steps) + +| Paso | Descripcion | Ubicaciones | +|------|-------------|-------------| +| ship_only | Entrega directa | Stock | +| pick_ship | Recoger, luego enviar | Stock → Output | +| pick_pack_ship | Recoger, empacar, enviar | Stock → Pack → Output | + +--- + +## 8. Hooks de Ciclo de Vida + +| Hook | Funcion | Descripcion | +|------|---------|-------------| +| pre_init_hook | - | Antes de instalacion | +| post_init_hook | _assign_default_mail_template_picking_id | Asigna plantilla correo | +| uninstall_hook | - | Al desinstalar | + +--- + +## 9. Estadisticas del Modulo + +| Metrica | Valor | +|---------|-------| +| Archivos de modelos | 30+ | +| Modelos principales | 15+ | +| Lineas de codigo estimadas | 80,000+ | +| Complejidad | ALTA | + +--- + +**Referencias:** +- Fuente: `addons/stock/` +- Manifest: `addons/stock/__manifest__.py` diff --git a/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-account.md b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-account.md new file mode 100644 index 000000000..0b025686a --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-account.md @@ -0,0 +1,237 @@ +# Modelo de Datos: Account + +**Modulo:** account +**Total Modelos:** 55 +**Modelos Documentados:** 6 (principales) + +--- + +## 1. Indice de Modelos Principales + +| Modelo | Descripcion | Campos | Archivo | +|--------|-------------|--------|---------| +| account.move | Asientos/Facturas | 50+ | account_move.py | +| account.move.line | Lineas | 40+ | account_move_line.py | +| account.account | Cuentas | 15+ | account_account.py | +| account.journal | Diarios | 20+ | account_journal.py | +| account.tax | Impuestos | 20+ | account_tax.py | +| account.payment | Pagos | 25+ | account_payment.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 account.move (Asientos/Facturas) + +**Archivo:** `models/account_move.py` +**Descripcion:** Journal Entry / Invoice +**Hereda de:** portal.mixin, mail.thread, mail.activity.mixin, sequence.mixin + +#### Estado Principal (state) + +| Estado | Descripcion | +|--------|-------------| +| draft | Borrador | +| posted | Publicado | +| cancel | Cancelado | + +#### Estado de Pago (payment_state) + +| Estado | Descripcion | +|--------|-------------| +| not_paid | No pagado | +| in_payment | En proceso de pago | +| paid | Pagado | +| partial | Parcialmente pagado | +| reversed | Revertido | +| blocked | Bloqueado | + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | No | Numero de asiento | +| ref | Char | No | Referencia | +| date | Date | Si | Fecha del asiento | +| state | Selection | Si | Estado | +| move_type | Selection | Si | Tipo de movimiento | +| journal_id | Many2one | Si | Diario | +| company_id | Many2one | Si | Empresa | +| currency_id | Many2one | Si | Moneda | +| partner_id | Many2one | No | Socio | +| line_ids | One2many | No | Lineas | + +#### Campos de Facturacion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| invoice_date | Date | Fecha factura | +| invoice_date_due | Date | Fecha vencimiento | +| invoice_payment_term_id | Many2one | Terminos de pago | +| invoice_line_ids | One2many | Lineas de factura | +| amount_untaxed | Monetary | Subtotal | +| amount_tax | Monetary | Impuestos | +| amount_total | Monetary | Total | +| amount_residual | Monetary | Monto debido | +| payment_state | Selection | Estado de pago | + +--- + +### 2.2 account.move.line (Lineas) + +**Archivo:** `models/account_move_line.py` +**Descripcion:** Journal Item +**Hereda de:** analytic.mixin + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| move_id | Many2one | Si | Asiento padre | +| account_id | Many2one | Si | Cuenta contable | +| name | Char | No | Etiqueta | +| debit | Monetary | No | Debito | +| credit | Monetary | No | Credito | +| balance | Monetary | No | Saldo (computed) | +| amount_currency | Monetary | No | Monto en moneda | +| currency_id | Many2one | Si | Moneda | +| partner_id | Many2one | No | Socio | +| tax_ids | Many2many | No | Impuestos | +| tax_line_id | Many2one | No | Impuesto origen | +| reconciled | Boolean | No | Reconciliado | +| amount_residual | Monetary | No | Pendiente | +| full_reconcile_id | Many2one | No | Reconciliacion completa | + +--- + +### 2.3 account.account (Cuentas) + +**Archivo:** `models/account_account.py` +**Descripcion:** Account + +#### Campos + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| code | Char | Si | Codigo de cuenta | +| name | Char | Si | Nombre | +| account_type | Selection | Si | Tipo de cuenta | +| reconcile | Boolean | Si | Permite reconciliacion | +| currency_id | Many2one | No | Moneda especifica | +| company_id | Many2one | Si | Empresa | +| active | Boolean | No | Activo | + +--- + +### 2.4 account.journal (Diarios) + +**Archivo:** `models/account_journal.py` +**Descripcion:** Journal + +#### Campos + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre | +| code | Char | Si | Codigo (5 chars) | +| type | Selection | Si | Tipo de diario | +| default_account_id | Many2one | No | Cuenta por defecto | +| company_id | Many2one | Si | Empresa | +| currency_id | Many2one | No | Moneda | + +--- + +### 2.5 account.tax (Impuestos) + +**Archivo:** `models/account_tax.py` +**Descripcion:** Tax + +#### Campos + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre | +| type_tax_use | Selection | Si | Uso: sale, purchase, none | +| amount_type | Selection | Si | Tipo: percent, fixed, group, division | +| amount | Float | No | Monto/porcentaje | +| company_id | Many2one | Si | Empresa | +| children_tax_ids | One2many | No | Impuestos hijos (si grupo) | + +--- + +### 2.6 account.payment (Pagos) + +**Archivo:** `models/account_payment.py` +**Descripcion:** Payment + +#### Estado + +| Estado | Descripcion | +|--------|-------------| +| draft | Borrador | +| in_process | En proceso | +| paid | Pagado | +| canceled | Cancelado | +| rejected | Rechazado | + +#### Campos + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | No | Numero | +| state | Selection | Si | Estado | +| date | Date | Si | Fecha | +| amount | Monetary | Si | Monto | +| payment_type | Selection | Si | outbound/inbound | +| partner_type | Selection | Si | customer/supplier | +| partner_id | Many2one | No | Socio | +| journal_id | Many2one | Si | Diario | +| move_id | Many2one | No | Asiento | + +--- + +## 3. Diagrama Entidad-Relacion + +``` +┌───────────────────────┐ line_ids ┌───────────────────────┐ +│ account.move │────────────────────►│ account.move.line │ +│ │ │ │ +│ - name │ move_id │ - debit │ +│ - state │◄─────────────────────│ - credit │ +│ - move_type │ │ - balance │ +│ - amount_total │ │ - account_id ──────►│ +└───────────┬───────────┘ │ - partner_id │ + │ │ - tax_ids │ + │ journal_id └───────────┬───────────┘ + ▼ │ +┌───────────────────────┐ │ account_id +│ account.journal │ ▼ +│ │ ┌───────────────────────┐ +│ - type │ │ account.account │ +│ - code │ │ │ +│ - default_account_id │ │ - code │ +└───────────────────────┘ │ - account_type │ + │ - reconcile │ + └───────────────────────┘ + +┌───────────────────────┐ move_id ┌───────────────────────┐ +│ account.payment │────────────────────►│ account.move │ +│ │ │ │ +│ - state │ │ (genera asiento) │ +│ - amount │ │ │ +│ - payment_type │ └───────────────────────┘ +└───────────────────────┘ + +┌───────────────────────┐ +│ account.tax │ +│ │ +│ - amount_type │ +│ - amount │ +│ - type_tax_use │ +└───────────────────────┘ +``` + +--- + +**Referencias:** +- Carpeta models: `addons/account/models/` diff --git a/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-analytic.md b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-analytic.md new file mode 100644 index 000000000..9940a0d9b --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-analytic.md @@ -0,0 +1,271 @@ +# Modelo de Datos: Analytic + +**Modulo:** analytic +**Total Modelos:** 5 +**Modelos Documentados:** 5 + +--- + +## 1. Indice de Modelos + +| Modelo | Descripcion | Archivo | +|--------|-------------|---------| +| account.analytic.account | Cuentas analiticas | analytic_account.py | +| account.analytic.line | Lineas analiticas | analytic_line.py | +| account.analytic.plan | Planes analiticos | analytic_plan.py | +| account.analytic.applicability | Reglas aplicabilidad | analytic_plan.py | +| account.analytic.distribution.model | Modelos distribucion | analytic_distribution_model.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 account.analytic.account (Cuentas Analiticas) + +**Archivo:** `models/analytic_account.py` +**Descripcion:** Analytic Account +**Hereda de:** mail.thread + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre cuenta | +| code | Char | No | Codigo referencia | +| active | Boolean | No | Activa (default=True) | +| plan_id | Many2one | Si | Plan analitico | +| root_plan_id | Many2one | No | Plan raiz (related) | +| company_id | Many2one | No | Empresa | +| partner_id | Many2one | No | Socio asociado | + +#### Campos Monetarios (Computed) + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| balance | Monetary | Saldo (credit - debit) | +| debit | Monetary | Total debitos | +| credit | Monetary | Total creditos | +| currency_id | Many2one | Moneda empresa | + +#### Campos de Relacion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| line_ids | One2many | Lineas analiticas | + +--- + +### 2.2 account.analytic.line (Lineas Analiticas) + +**Archivo:** `models/analytic_line.py` +**Descripcion:** Analytic Line +**Hereda de:** analytic.plan.fields.mixin + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Descripcion | +| date | Date | Si | Fecha | +| account_id | Many2one | No | Cuenta principal | +| auto_account_id | Many2one | No | Cuenta auto (computed) | +| partner_id | Many2one | No | Socio | +| user_id | Many2one | No | Usuario | +| company_id | Many2one | Si | Empresa | + +#### Campos Monetarios + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| amount | Monetary | Monto (requerido, default=0) | +| currency_id | Many2one | Moneda empresa (related) | +| unit_amount | Float | Cantidad (default=0) | +| product_uom_id | Many2one | Unidad de medida | + +#### Campos de Distribucion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| analytic_distribution | Json | Distribucion {account_ids: %} | + +--- + +### 2.3 account.analytic.plan (Planes Analiticos) + +**Archivo:** `models/analytic_plan.py` +**Descripcion:** Analytic Plan +**Estructura:** Jerarquica (_parent_store=True) + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre plan | +| description | Text | No | Descripcion | +| sequence | Integer | No | Orden (default=10) | +| color | Integer | No | Color (1-11) | +| company_id | Many2one | No | Empresa | + +#### Campos Jerarquicos + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| parent_id | Many2one | Plan padre | +| children_ids | One2many | Planes hijo | +| root_id | Many2one | Plan raiz (computed) | +| complete_name | Char | Nombre completo jerarquico | +| parent_path | Char | Ruta jerarquica | + +#### Campos de Configuracion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| default_applicability | Selection | Aplicabilidad default | +| applicability_ids | One2many | Reglas aplicabilidad | +| account_ids | One2many | Cuentas del plan | +| account_count | Integer | Cuentas directas | +| all_account_count | Integer | Cuentas incluyendo hijos | + +--- + +### 2.4 account.analytic.applicability (Aplicabilidad) + +**Archivo:** `models/analytic_plan.py` +**Descripcion:** Analytic Plan Applicability + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| analytic_plan_id | Many2one | Si | Plan aplicable | +| business_domain | Selection | No | Dominio (general) | +| applicability | Selection | Si | Tipo aplicabilidad | +| company_id | Many2one | No | Empresa especifica | + +#### Valores de Aplicabilidad + +| Valor | Descripcion | +|-------|-------------| +| optional | Opcional | +| mandatory | Obligatorio (debe sumar 100%) | +| unavailable | No disponible | + +--- + +### 2.5 account.analytic.distribution.model + +**Archivo:** `models/analytic_distribution_model.py` +**Descripcion:** Analytic Distribution Model +**Hereda de:** analytic.mixin + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| sequence | Integer | Orden prioridad | +| partner_id | Many2one | Socio especifico | +| partner_category_id | Many2one | Categoria socio | +| company_id | Many2one | Empresa | +| analytic_distribution | Json | Distribucion pre-configurada | + +--- + +## 3. Mixins + +### 3.1 analytic.mixin + +**Archivo:** `models/analytic_mixin.py` +**Proposito:** Agregar distribucion analitica a cualquier modelo + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| analytic_distribution | Json | Distribucion {ids: %} | +| distribution_analytic_account_ids | Many2many | Cuentas en distribucion | + +#### Metodos Clave + +| Metodo | Descripcion | +|--------|-------------| +| _compute_analytic_distribution | Calcula desde cuentas | +| _search_analytic_distribution | Busqueda por nombre cuenta | +| _merge_distribution | Fusiona distribuciones | +| _validate_distribution | Valida 100% para obligatorios | +| _sanitize_values | Redondea porcentajes | + +### 3.2 analytic.plan.fields.mixin + +**Archivo:** `models/analytic_line.py` +**Proposito:** Campos dinamicos por cada plan + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| account_id | Many2one | Cuenta principal (Project) | +| auto_account_id | Many2one | Cuenta auto-detectada | + +#### Campos Dinamicos Generados + +``` +Plan "Project" → account_id +Otros planes → x_plan{id}_id +Planes hijo → x_account_id_{depth}_id +``` + +--- + +## 4. Diagrama Entidad-Relacion + +``` +┌───────────────────────┐ account_ids ┌───────────────────────┐ +│ account.analytic.plan │──────────────────►│account.analytic.account│ +│ │ │ │ +│ - name │ plan_id │ - name │ +│ - parent_id │◄───────────────────│ - balance │ +│ - default_applicability│ │ - debit, credit │ +└───────────┬───────────┘ └───────────┬───────────┘ + │ │ + children_ids line_ids + ▼ ▼ +┌───────────────────────┐ ┌───────────────────────┐ +│ account.analytic.plan │ │ account.analytic.line │ +│ (hijo) │ │ │ +└───────────────────────┘ │ - amount │ + │ - account_id │ +┌───────────────────────┐ │ - analytic_distribution│ +│account.analytic. │ └───────────────────────┘ +│applicability │ +│ │ +│ - applicability │ +│ - business_domain │ +└───────────────────────┘ + +┌───────────────────────┐ +│account.analytic. │ +│distribution.model │ +│ │ +│ - partner_id │ +│ - analytic_distribution│ +└───────────────────────┘ +``` + +--- + +## 5. Formato JSON de Distribucion + +```json +// Ejemplo: 50% a cuentas 1+2, 50% a cuenta 3 +{ + "1,2": 50.0, + "3": 50.0 +} + +// Las claves son IDs de cuentas separados por coma +// Los valores son porcentajes (Float) +// Total debe ser 100% para planes obligatorios +``` + +--- + +## 6. Indice GIN para Busqueda JSON + +PostgreSQL GIN index en `analytic_distribution` para busquedas rapidas en campos JSON. + +--- + +**Referencias:** +- Carpeta models: `addons/analytic/models/` diff --git a/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-base.md b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-base.md new file mode 100644 index 000000000..63472589f --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-base.md @@ -0,0 +1,423 @@ +# Modelo de Datos: Base (Kernel) + +**Modulo:** base +**Total Modelos Principales:** 10 (documentados) +**Total Modelos Completos:** ~50 + +--- + +## 1. Indice de Modelos Principales + +| Modelo | Descripcion | Campos | Archivo | +|--------|-------------|--------|---------| +| res.partner | Contactos/Clientes/Proveedores | 40+ | res_partner.py | +| res.users | Usuarios del sistema | 25+ | res_users.py | +| res.company | Empresas | 30+ | res_company.py | +| res.groups | Grupos de seguridad | 15+ | res_groups.py | +| ir.model | Metamodelo ORM | 10+ | ir_model.py | +| ir.rule | Reglas de acceso | 10+ | ir_rule.py | +| ir.cron | Tareas programadas | 12+ | ir_cron.py | +| ir.sequence | Secuencias | 12+ | ir_sequence.py | +| res.currency | Monedas | 15+ | res_currency.py | +| res.country | Paises | 12+ | res_country.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 res.partner (Contactos) + +**Archivo:** `models/res_partner.py` +**Descripcion:** Gestiona contactos, clientes y proveedores +**Hereda de:** format.address.mixin, format.vat.label.mixin, avatar.mixin + +#### Campos Principales + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char | No | - | Nombre del contacto | +| complete_name | Char | No | Computed | Nombre completo (con padre) | +| email | Char | No | - | Correo electronico | +| phone | Char | No | - | Telefono | +| mobile | Char | No | - | Movil | +| website | Char | No | - | Sitio web | +| vat | Char | No | - | Identificacion fiscal | +| ref | Char | No | - | Referencia interna | +| active | Boolean | No | True | Activo | +| is_company | Boolean | No | False | Es empresa | +| employee | Boolean | No | False | Es empleado | +| function | Char | No | - | Puesto de trabajo | +| type | Selection | No | contact | Tipo: contact, invoice, delivery, other | +| lang | Selection | No | - | Idioma | +| tz | Selection | No | - | Zona horaria | + +#### Campos de Direccion + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| street | Char | No | Calle | +| street2 | Char | No | Calle 2 | +| city | Char | No | Ciudad | +| zip | Char | No | Codigo postal | +| state_id | Many2one | No | Estado/Provincia | +| country_id | Many2one | No | Pais | + +#### Campos Relacionales + +| Campo | Tipo | Relacion | Descripcion | +|-------|------|----------|-------------| +| parent_id | Many2one | res.partner | Empresa padre | +| child_ids | One2many | res.partner | Contactos hijos | +| company_id | Many2one | res.company | Empresa | +| user_ids | One2many | res.users | Usuarios vinculados | +| bank_ids | One2many | res.partner.bank | Cuentas bancarias | +| category_id | Many2many | res.partner.category | Categorias | +| country_id | Many2one | res.country | Pais | +| state_id | Many2one | res.country.state | Estado | + +#### Constraints + +| Nombre | Tipo | Campos | Mensaje | +|--------|------|--------|---------| +| check_name | Python | name, is_company | Nombre requerido si es empresa | + +--- + +### 2.2 res.users (Usuarios) + +**Archivo:** `models/res_users.py` +**Descripcion:** Usuarios del sistema +**Hereda de:** res.partner (via _inherits) + +#### Campos Principales + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| login | Char | Si | - | Nombre de usuario | +| password | Char | No | Computed | Contrasena (cifrada) | +| active | Boolean | No | True | Usuario activo | +| share | Boolean | No | Computed | Usuario compartido | +| signature | Html | No | Computed | Firma de email | +| role | Selection | No | Computed | Rol: group_user, group_system | + +#### Campos Relacionales + +| Campo | Tipo | Relacion | Descripcion | +|-------|------|----------|-------------| +| partner_id | Many2one | res.partner | Contacto (requerido) | +| company_id | Many2one | res.company | Empresa default | +| company_ids | Many2many | res.company | Empresas accesibles | +| group_ids | Many2many | res.groups | Grupos | +| all_group_ids | Many2many | res.groups | Grupos + implicados | +| action_id | Many2one | ir.actions.actions | Accion inicial | +| api_key_ids | One2many | res.users.apikeys | Claves API | + +#### Metodos Importantes + +| Metodo | Descripcion | +|--------|-------------| +| authenticate() | Autenticar usuario | +| change_password() | Cambiar contrasena | +| has_group() | Verificar membresia de grupo | +| _is_internal() | Es usuario interno? | +| _is_admin() | Es administrador? | + +--- + +### 2.3 res.company (Empresas) + +**Archivo:** `models/res_company.py` +**Descripcion:** Estructura de empresas +**Parent Store:** Si (jerarquia) + +#### Campos Principales + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char | Si | Related | Nombre | +| active | Boolean | No | True | Activa | +| sequence | Integer | No | 10 | Orden | +| currency_id | Many2one | Si | - | Moneda | +| phone | Char | No | Related | Telefono | +| email | Char | No | Related | Email | +| website | Char | No | Related | Sitio web | +| vat | Char | No | Related | NIF | + +#### Campos de Diseno + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| logo | Binary | Logotipo | +| logo_web | Binary | Logo optimizado web | +| font | Selection | Fuente corporativa | +| primary_color | Char | Color primario (hex) | +| secondary_color | Char | Color secundario | +| paperformat_id | Many2one | Formato de papel | + +#### Campos Relacionales + +| Campo | Tipo | Relacion | Descripcion | +|-------|------|----------|-------------| +| partner_id | Many2one | res.partner | Contacto empresa | +| parent_id | Many2one | res.company | Empresa padre | +| child_ids | One2many | res.company | Sucursales | +| root_id | Many2one | res.company | Empresa raiz | +| user_ids | Many2many | res.users | Usuarios | +| bank_ids | One2many | res.partner.bank | Cuentas bancarias | + +--- + +### 2.4 res.groups (Grupos de Seguridad) + +**Archivo:** `models/res_groups.py` +**Descripcion:** Control de acceso + +#### Campos + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre del grupo | +| full_name | Char | No | Nombre completo | +| comment | Text | No | Descripcion | +| share | Boolean | No | Grupo compartido | +| sequence | Integer | No | Orden | + +#### Relaciones + +| Campo | Tipo | Relacion | Descripcion | +|-------|------|----------|-------------| +| user_ids | Many2many | res.users | Usuarios | +| implied_ids | Many2many | res.groups | Grupos implicados | +| model_access | One2many | ir.model.access | Derechos de acceso | +| rule_groups | Many2many | ir.rule | Reglas de registro | +| menu_access | Many2many | ir.ui.menu | Acceso a menus | + +--- + +### 2.5 ir.rule (Reglas de Acceso) + +**Archivo:** `models/ir_rule.py` +**Descripcion:** Control de acceso a nivel de registro + +#### Campos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char | No | - | Nombre | +| active | Boolean | No | True | Activa | +| domain_force | Text | No | - | Dominio/filtro | +| perm_read | Boolean | No | True | Permite lectura | +| perm_write | Boolean | No | True | Permite escritura | +| perm_create | Boolean | No | True | Permite creacion | +| perm_unlink | Boolean | No | True | Permite eliminacion | +| global | Boolean | No | Computed | Regla global | + +#### Relaciones + +| Campo | Tipo | Relacion | Descripcion | +|-------|------|----------|-------------| +| model_id | Many2one | ir.model | Modelo (requerido) | +| groups | Many2many | res.groups | Grupos afectados | + +--- + +### 2.6 ir.cron (Tareas Programadas) + +**Archivo:** `models/ir_cron.py` +**Descripcion:** Trabajos automaticos + +#### Campos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| cron_name | Char | No | Computed | Nombre | +| active | Boolean | No | True | Activo | +| interval_number | Integer | Si | 1 | Repetir cada X | +| interval_type | Selection | Si | months | Unidad: minutes, hours, days, weeks, months | +| nextcall | Datetime | Si | - | Proxima ejecucion | +| lastcall | Datetime | No | - | Ultima ejecucion | +| priority | Integer | No | 5 | Prioridad (0=alta) | +| failure_count | Integer | No | 0 | Fallos consecutivos | + +#### Relaciones + +| Campo | Tipo | Relacion | Descripcion | +|-------|------|----------|-------------| +| ir_actions_server_id | Many2one | ir.actions.server | Accion a ejecutar | +| user_id | Many2one | res.users | Usuario ejecutor | + +--- + +### 2.7 ir.sequence (Secuencias) + +**Archivo:** `models/ir_sequence.py` +**Descripcion:** Generacion de numeros unicos + +#### Campos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char | Si | - | Nombre | +| code | Char | No | - | Codigo referencia | +| prefix | Char | No | - | Prefijo | +| suffix | Char | No | - | Sufijo | +| number_next | Integer | Si | 1 | Proximo numero | +| number_increment | Integer | Si | 1 | Incremento | +| padding | Integer | Si | 0 | Relleno con ceros | +| implementation | Selection | Si | standard | Tipo: standard, no_gap | +| use_date_range | Boolean | No | False | Subsecuencias por fecha | + +--- + +### 2.8 res.currency (Monedas) + +**Archivo:** `models/res_currency.py` +**Descripcion:** Gestion de monedas + +#### Campos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char(3) | Si | - | Codigo ISO (USD, EUR) | +| iso_numeric | Integer | No | - | Codigo numerico ISO | +| full_name | Char | No | - | Nombre completo | +| symbol | Char | Si | - | Simbolo ($, E) | +| rounding | Float | No | 0.01 | Factor redondeo | +| decimal_places | Integer | No | Computed | Decimales | +| position | Selection | No | after | Posicion simbolo | +| active | Boolean | No | True | Activa | + +#### Relaciones + +| Campo | Tipo | Relacion | Descripcion | +|-------|------|----------|-------------| +| rate_ids | One2many | res.currency.rate | Historico de tasas | + +--- + +### 2.9 res.country (Paises) + +**Archivo:** `models/res_country.py` +**Descripcion:** Informacion de paises + +#### Campos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char | Si | - | Nombre | +| code | Char(2) | Si | - | Codigo ISO | +| phone_code | Integer | No | - | Codigo telefonico | +| address_format | Text | No | - | Formato direccion | +| vat_label | Char | No | - | Etiqueta VAT | +| state_required | Boolean | No | False | Requiere estado | +| zip_required | Boolean | No | True | Requiere CP | + +#### Relaciones + +| Campo | Tipo | Relacion | Descripcion | +|-------|------|----------|-------------| +| currency_id | Many2one | res.currency | Moneda | +| state_ids | One2many | res.country.state | Estados/Provincias | +| country_group_ids | Many2many | res.country.group | Grupos de paises | + +--- + +## 3. Diagrama Entidad-Relacion + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ DATOS MAESTROS │ +└─────────────────────────────────────────────────────────────────────────┘ + +┌───────────────┐ parent_id ┌───────────────┐ +│ res.partner │◄───────────────────│ res.partner │ +│ │ │ │ +│ - name │ child_ids │ (jerarquia) │ +│ - email │────────────────────►│ │ +│ - vat │ └───────────────┘ +│ - type │ +└───────┬───────┘ + │ partner_id (required) + │ + ▼ +┌───────────────┐ company_ids ┌───────────────┐ +│ res.users │◄──────────────────►│ res.company │ +│ │ (Many2many) │ │ +│ - login │ │ - name │ +│ - password │────────────────────►│ - currency_id│ +│ - active │ company_id │ - logo │ +└───────┬───────┘ └───────┬───────┘ + │ │ + │ group_ids │ parent_id + ▼ ▼ +┌───────────────┐ ┌───────────────┐ +│ res.groups │ │ res.company │ +│ │ │ (jerarquia) │ +│ - name │ └───────────────┘ +│ - sequence │ +└───────┬───────┘ + │ + │ rule_groups / model_access + ▼ +┌───────────────┐ ┌───────────────┐ +│ ir.rule │ │ ir.model.access│ +│ │ │ │ +│ - domain │ │ - perm_read │ +│ - perm_* │ │ - perm_write │ +└───────┬───────┘ └───────────────┘ + │ model_id + ▼ +┌───────────────┐ +│ ir.model │ +│ │ +│ - model │ +│ - name │ +└───────────────┘ + + +┌─────────────────────────────────────────────────────────────────────────┐ +│ AUTOMATIZACION │ +└─────────────────────────────────────────────────────────────────────────┘ + +┌───────────────┐ ir_actions_server_id +│ ir.cron │─────────────────────────►┌───────────────────┐ +│ │ │ ir.actions.server │ +│ - nextcall │ │ │ +│ - interval │ │ - code │ +│ - priority │ │ - model_id │ +└───────────────┘ └───────────────────┘ + +┌───────────────┐ date_range_ids +│ ir.sequence │────────────────────────►┌─────────────────────┐ +│ │ │ir.sequence.date_range│ +│ - prefix │ │ │ +│ - number_next│ │ - date_from │ +│ - padding │ │ - date_to │ +└───────────────┘ └─────────────────────┘ + + +┌─────────────────────────────────────────────────────────────────────────┐ +│ GEOGRAFICO │ +└─────────────────────────────────────────────────────────────────────────┘ + +┌───────────────┐ state_ids ┌───────────────────┐ +│ res.country │───────────────────►│ res.country.state │ +│ │ │ │ +│ - code (ES) │ │ - name │ +│ - phone_code │ │ - code │ +└───────┬───────┘ └───────────────────┘ + │ currency_id + ▼ +┌───────────────┐ rate_ids ┌───────────────────┐ +│ res.currency │───────────────────►│ res.currency.rate │ +│ │ │ │ +│ - name (USD) │ │ - rate │ +│ - symbol ($) │ │ - name (date) │ +└───────────────┘ └───────────────────┘ +``` + +--- + +**Referencias:** +- Carpeta models: `odoo/addons/base/models/` +- Total archivos: 49 diff --git a/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-crm.md b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-crm.md new file mode 100644 index 000000000..580f88053 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-crm.md @@ -0,0 +1,252 @@ +# Modelo de Datos: CRM + +**Modulo:** crm +**Total Modelos:** 7+ +**Modelos Documentados:** 5 (principales) + +--- + +## 1. Indice de Modelos Principales + +| Modelo | Descripcion | Campos | Archivo | +|--------|-------------|--------|---------| +| crm.lead | Leads/Opportunities | 80+ | crm_lead.py | +| crm.stage | Etapas pipeline | 10+ | crm_stage.py | +| crm.team | Equipos venta | 15+ | crm_team.py | +| crm.team.member | Miembros | 8+ | crm_team_member.py | +| crm.lost.reason | Razones perdida | 3 | crm_lost_reason.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 crm.lead (Lead/Opportunity) + +**Archivo:** `models/crm_lead.py` +**Descripcion:** Lead +**Hereda de:** mail.thread.cc, mail.thread.blacklist, mail.thread.phone, mail.activity.mixin, utm.mixin, format.address.mixin + +#### Campos de Pipeline + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre oportunidad | +| type | Selection | Si | lead/opportunity | +| stage_id | Many2one | No | Etapa actual | +| priority | Selection | No | Prioridad (0-3) | +| won_status | Selection | No | won/lost/pending (computed) | +| active | Boolean | No | Activa (default=True) | + +#### Campos de Probabilidad + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| probability | Float | Probabilidad manual (0-100) | +| automated_probability | Float | Probabilidad PLS | +| is_automated_probability | Boolean | probability == automated | + +#### Campos de Ingresos + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| expected_revenue | Monetary | Ingreso esperado | +| prorated_revenue | Monetary | revenue * probability/100 | +| recurring_revenue | Monetary | Ingresos recurrentes | +| recurring_plan | Many2one | Plan de recurrencia | +| recurring_revenue_monthly | Monetary | MRR mensual | + +#### Campos de Fechas + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| date_closed | Datetime | Fecha cierre | +| date_open | Datetime | Fecha asignacion | +| date_conversion | Datetime | Fecha conversion | +| date_deadline | Date | Cierre esperado | +| date_last_stage_update | Datetime | Ultima actualizacion etapa | +| day_open | Float | Dias para asignar | +| day_close | Float | Dias para cerrar | + +#### Campos de Contacto + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| partner_id | Many2one | Cliente vinculado | +| commercial_partner_id | Many2one | Empresa cliente | +| partner_name | Char | Nombre empresa | +| contact_name | Char | Nombre contacto | +| email_from | Char | Email | +| phone | Char | Telefono | +| website | Char | Sitio web | +| function | Char | Cargo | + +#### Campos de Organizacion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| user_id | Many2one | Vendedor | +| team_id | Many2one | Equipo de ventas | +| company_id | Many2one | Empresa | +| tag_ids | Many2many | Etiquetas | +| lost_reason_id | Many2one | Razon de perdida | + +#### Campos UTM + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| campaign_id | Many2one | Campana UTM | +| medium_id | Many2one | Medio UTM | +| source_id | Many2one | Fuente UTM | + +#### Restricciones + +``` +CHECK(probability >= 0 and probability <= 100) +``` + +--- + +### 2.2 crm.stage (Etapas) + +**Archivo:** `models/crm_stage.py` +**Descripcion:** CRM Stages + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre etapa | +| sequence | Integer | No | Orden (default=1) | +| is_won | Boolean | No | Etapa ganadora | +| fold | Boolean | No | Plegada en kanban | +| team_ids | Many2many | No | Equipos asociados | +| rotting_threshold_days | Integer | No | Dias antes de "podrido" | +| requirements | Text | No | Requisitos internos | +| color | Integer | No | Color UI | +| team_count | Integer | No | Cantidad equipos | + +--- + +### 2.3 crm.team (Equipos de Venta) + +**Archivo:** `models/crm_team.py` +**Descripcion:** Sales Team (extension) +**Hereda de:** crm.team (sales_team) + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| use_leads | Boolean | Habilita leads | +| use_opportunities | Boolean | Habilita opportunities | +| alias_id | Many2one | Email alias | +| assignment_enabled | Boolean | Asignacion automatica | +| assignment_max | Integer | Capacidad mensual | +| assignment_domain | Char | Dominio asignacion | +| lead_unassigned_count | Integer | Leads sin asignar | +| lead_properties_definition | PropertiesDefinition | Propiedades custom | +| crm_team_member_ids | One2many | Miembros | + +--- + +### 2.4 crm.team.member (Miembros) + +**Archivo:** `models/crm_team_member.py` +**Descripcion:** Sales Team Member + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| assignment_enabled | Boolean | Asignacion habilitada | +| assignment_domain | Char | Dominio asignacion | +| assignment_domain_preferred | Char | Dominio preferido | +| assignment_optout | Boolean | Pausar asignacion | +| assignment_max | Integer | Capacidad mensual (default=30) | +| lead_day_count | Integer | Leads ultimas 24h | +| lead_month_count | Integer | Leads ultimos 30 dias | + +--- + +### 2.5 crm.lost.reason (Razones de Perdida) + +**Archivo:** `models/crm_lost_reason.py` +**Descripcion:** Opportunity Lost Reason + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Descripcion razon | +| active | Boolean | No | Activo | +| leads_count | Integer | No | Cantidad leads | + +--- + +## 3. Diagrama Entidad-Relacion + +``` +┌───────────────────────┐ stage_id ┌───────────────────────┐ +│ crm.lead │─────────────────────►│ crm.stage │ +│ │ │ │ +│ - type │ team_ids │ - is_won │ +│ - probability │◄─────────────────────│ - fold │ +│ - won_status │ │ - sequence │ +│ - expected_revenue │ └───────────────────────┘ +└───────────┬───────────┘ + │ + │ user_id, team_id + ▼ +┌───────────────────────┐ member_ids ┌───────────────────────┐ +│ crm.team │─────────────────────►│ crm.team.member │ +│ │ │ │ +│ - use_leads │ │ - assignment_max │ +│ - use_opportunities │ │ - lead_month_count │ +│ - assignment_enabled │ └───────────────────────┘ +└───────────────────────┘ + +┌───────────────────────┐ lost_reason_id ┌───────────────────────┐ +│ crm.lead │─────────────────────►│ crm.lost.reason │ +│ │ │ │ +│ (perdida) │ │ - leads_count │ +└───────────────────────┘ └───────────────────────┘ +``` + +--- + +## 4. Constantes del Modulo + +```python +# Prioridades disponibles +AVAILABLE_PRIORITIES = [ + ('0', 'Low'), + ('1', 'Medium'), + ('2', 'High'), + ('3', 'Very High'), +] + +# Tipos de registro +type = Selection([ + ('lead', 'Lead'), + ('opportunity', 'Opportunity') +]) + +# Estados won/lost +won_status = Selection([ + ('won', 'Won'), + ('lost', 'Lost'), + ('pending', 'Pending'), +]) +``` + +--- + +## 5. Modelo de Scoring + +### crm.lead.scoring.frequency + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| variable | Char | Campo analizado | +| value | Char | Valor especifico | +| won_count | Float | Conteo ganadas + 0.1 | +| lost_count | Float | Conteo perdidas + 0.1 | +| team_id | Many2one | Equipo (NULL = global) | + +--- + +**Referencias:** +- Carpeta models: `addons/crm/models/` +- Constantes: `addons/crm/models/crm_stage.py` diff --git a/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-hr.md b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-hr.md new file mode 100644 index 000000000..f66e582ea --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-hr.md @@ -0,0 +1,278 @@ +# Modelo de Datos: HR (Recursos Humanos) + +**Modulo:** hr +**Total Modelos:** 10+ +**Modelos Documentados:** 6 (principales) + +--- + +## 1. Indice de Modelos Principales + +| Modelo | Descripcion | Campos | Archivo | +|--------|-------------|--------|---------| +| hr.employee | Empleados | 100+ | hr_employee.py | +| hr.department | Departamentos | 15+ | hr_department.py | +| hr.job | Puestos trabajo | 15+ | hr_job.py | +| hr.version | Versiones empleado | 40+ | hr_version.py | +| hr.work.location | Ubicaciones | 6 | hr_work_location.py | +| hr.employee.category | Categorias | 3 | hr_employee_category.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 hr.employee (Empleados) + +**Archivo:** `models/hr_employee.py` +**Descripcion:** Employee +**Hereda de:** mail.thread.main.attachment, mail.activity.mixin, resource.mixin, avatar.mixin + +#### Campos de Identificacion + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre (related resource_id.name) | +| barcode | Char | No | Badge ID (unico) | +| pin | Char | No | PIN seguridad | +| active | Boolean | No | Activo (default=True) | +| resource_id | Many2one | Si | Recurso asociado | + +#### Campos Organizacionales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| user_id | Many2one | No | Usuario Odoo | +| company_id | Many2one | Si | Empresa | +| department_id | Many2one | No | Departamento | +| job_id | Many2one | No | Puesto de trabajo | +| parent_id | Many2one | No | Gerente directo | +| coach_id | Many2one | No | Coach/Mentor | +| child_ids | One2many | No | Subordinados directos | + +#### Campos de Contacto Laboral + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| work_phone | Char | Telefono laboral | +| mobile_phone | Char | Movil laboral | +| work_email | Char | Email laboral | +| work_contact_id | Many2one | Contacto res.partner | +| work_location_id | Many2one | Ubicacion de trabajo | + +#### Campos Personales (groups="hr.group_hr_user") + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| legal_name | Char | Nombre legal | +| birthday | Date | Fecha nacimiento | +| place_of_birth | Char | Lugar nacimiento | +| country_of_birth | Many2one | Pais nacimiento | +| private_phone | Char | Telefono privado | +| private_email | Char | Email privado | +| identification_id | Char | ID nacional | +| passport_id | Char | Numero pasaporte | +| permit_no | Char | Permiso de trabajo | + +#### Campos de Versionamiento + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| version_ids | One2many | Historico versiones | +| current_version_id | Many2one | Version actual (stored) | +| version_id | Many2one | Version actual (computed) | + +#### Restricciones + +| Constraint | Descripcion | +|------------|-------------| +| barcode UNIQUE | Badge ID unico | +| user_company UNIQUE | Un usuario, una empresa | + +--- + +### 2.2 hr.department (Departamentos) + +**Archivo:** `models/hr_department.py` +**Descripcion:** HR Department +**Hereda de:** mail.thread, mail.activity.mixin +**Estructura:** Jerarquica (_parent_store=True) + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre departamento | +| complete_name | Char | No | Nombre completo jerarquico | +| active | Boolean | No | Activo (default=True) | +| company_id | Many2one | No | Empresa | +| parent_id | Many2one | No | Departamento padre | +| child_ids | One2many | No | Subdepartamentos | +| manager_id | Many2one | No | Gerente | +| member_ids | One2many | No | Empleados | +| jobs_ids | One2many | No | Puestos | +| total_employee | Integer | No | Total empleados (computed) | +| parent_path | Char | No | Ruta jerarquica | +| master_department_id | Many2one | No | Departamento raiz | + +--- + +### 2.3 hr.job (Puestos de Trabajo) + +**Archivo:** `models/hr_job.py` +**Descripcion:** Job Position +**Hereda de:** mail.thread + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre del puesto | +| active | Boolean | No | Activo | +| sequence | Integer | No | Orden (default=10) | +| department_id | Many2one | No | Departamento | +| company_id | Many2one | No | Empresa | +| employee_ids | One2many | No | Empleados en puesto | +| no_of_employee | Integer | No | Empleados actuales | +| expected_employees | Integer | No | Empleados esperados | +| no_of_recruitment | Integer | No | Vacantes (default=1) | +| description | Html | No | Descripcion | +| requirements | Text | No | Requisitos | +| contract_type_id | Many2one | No | Tipo contrato | + +#### Restriccion + +``` +name + company_id + department_id UNIQUE +``` + +--- + +### 2.4 hr.version (Versiones de Empleado) + +**Archivo:** `models/hr_version.py` +**Descripcion:** Employee Version History + +#### Campos de Contrato + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| employee_id | Many2one | Empleado (requerido) | +| date_version | Date | Fecha efectiva (requerido) | +| contract_date_start | Date | Inicio contrato | +| contract_date_end | Date | Fin contrato | +| trial_date_end | Date | Fin periodo prueba | +| wage | Monetary | Salario bruto mensual | +| contract_type_id | Many2one | Tipo contrato | +| structure_type_id | Many2one | Estructura salarial | + +#### Campos Personales + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| country_id | Many2one | Nacionalidad | +| sex | Selection | Genero (male/female/other) | +| marital | Selection | Estado civil | +| children | Integer | Hijos dependientes | +| identification_id | Char | ID nacional | +| ssnid | Char | Seguro social | +| passport_id | Char | Pasaporte | + +#### Estados de Version + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| is_current | Boolean | Version actual | +| is_past | Boolean | Version pasada | +| is_future | Boolean | Version futura | + +--- + +### 2.5 hr.work.location (Ubicaciones) + +**Archivo:** `models/hr_work_location.py` +**Descripcion:** Work Location + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre ubicacion | +| active | Boolean | No | Activo | +| company_id | Many2one | Si | Empresa | +| location_type | Selection | No | Tipo (home/office/other) | +| address_id | Many2one | Si | Direccion | +| location_number | Char | No | Numero ubicacion | + +--- + +### 2.6 hr.employee.category (Categorias) + +**Archivo:** `models/hr_employee_category.py` +**Descripcion:** Employee Category (Tags) + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre (unico) | +| color | Integer | No | Color (1-11) | +| employee_ids | Many2many | No | Empleados | + +--- + +## 3. Diagrama Entidad-Relacion + +``` +┌───────────────────────┐ child_ids ┌───────────────────────┐ +│ hr.department │◄─────────────────────│ hr.department │ +│ │ │ │ +│ - name │ parent_id │ (Subdepartamento) │ +│ - complete_name │─────────────────────►│ │ +│ - manager_id ──────┐ │ └───────────────────────┘ +└───────────┬─────────┘ │ + │ │ + member_ids │ + ▼ │ +┌───────────────────────┐ ┌───────────────────────┐ +│ hr.employee │◄──────────────────────│ hr.employee │ +│ │ parent_id │ (Subordinado) │ +│ - name │ └───────────────────────┘ +│ - department_id │ +│ - job_id ──────────┐ │ version_ids ┌───────────────────────┐ +│ - parent_id ───────┘ │─────────────────────►│ hr.version │ +│ - user_id │ │ │ +└───────────┬───────────┘ │ - date_version │ + │ │ - wage │ + │ job_id │ - contract_type_id │ + ▼ └───────────────────────┘ +┌───────────────────────┐ +│ hr.job │ +│ │ +│ - name │ +│ - department_id │ +│ - employee_ids │ +│ - no_of_recruitment │ +└───────────────────────┘ +``` + +--- + +## 4. Modelos de Configuracion + +### hr.contract.type + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| name | Char | Nombre (traducible) | +| code | Char | Codigo | +| sequence | Integer | Orden | +| country_id | Many2one | Pais | + +### hr.departure.reason + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| name | Char | Razon (traducible) | +| sequence | Integer | Orden | +| country_id | Many2one | Pais | + +--- + +**Referencias:** +- Carpeta models: `addons/hr/models/` diff --git a/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-product.md b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-product.md new file mode 100644 index 000000000..21ffbe62c --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-product.md @@ -0,0 +1,426 @@ +# Modelo de Datos: Product + +**Modulo:** product +**Total Modelos Principales:** 15+ +**Modelos Documentados:** 8 (prioritarios) + +--- + +## 1. Indice de Modelos Principales + +| Modelo | Descripcion | Campos | Archivo | +|--------|-------------|--------|---------| +| product.template | Plantilla de producto | 40+ | product_template.py | +| product.product | Variante de producto | 20+ | product_product.py | +| product.category | Categorias | 8 | product_category.py | +| product.pricelist | Listas de precios | 8 | product_pricelist.py | +| product.pricelist.item | Reglas de precio | 20+ | product_pricelist.py | +| product.attribute | Atributos | 10 | product_attribute.py | +| product.attribute.value | Valores de atributo | 10 | product_attribute.py | +| product.supplierinfo | Info de proveedor | 15 | product_supplierinfo.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 product.template (Plantilla de Producto) + +**Archivo:** `models/product_template.py` +**Descripcion:** Producto base con atributos y variantes +**Hereda de:** mail.thread, mail.activity.mixin, image.mixin + +#### Campos Principales + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char | Si | - | Nombre del producto | +| sequence | Integer | No | 1 | Orden de visualizacion | +| type | Selection | No | consu | Tipo: consu, service, combo | +| list_price | Float | No | 1.0 | Precio de venta | +| standard_price | Float | No | 0 | Costo | +| volume | Float | No | 0 | Volumen | +| weight | Float | No | 0 | Peso | +| sale_ok | Boolean | No | True | Vendible | +| purchase_ok | Boolean | No | Computed | Comprable | +| active | Boolean | No | True | Activo | +| color | Integer | No | 0 | Color indice | +| barcode | Char | No | Computed | Codigo de barras | +| default_code | Char | No | Computed | Referencia interna | +| is_favorite | Boolean | No | False | Favorito | + +#### Campos de Descripcion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| description | Html | Descripcion general | +| description_purchase | Text | Descripcion para compras | +| description_sale | Text | Descripcion para ventas | + +#### Campos Relacionales + +| Campo | Tipo | Relacion | Descripcion | +|-------|------|----------|-------------| +| categ_id | Many2one | product.category | Categoria | +| company_id | Many2one | res.company | Empresa | +| uom_id | Many2one | uom.uom | Unidad de medida (req) | +| uom_ids | Many2many | uom.uom | Empaques adicionales | +| currency_id | Many2one | res.currency | Moneda (computed) | +| seller_ids | One2many | product.supplierinfo | Proveedores | +| attribute_line_ids | One2many | product.template.attribute.line | Atributos | +| product_variant_ids | One2many | product.product | Variantes | +| pricelist_rule_ids | One2many | product.pricelist.item | Reglas de precio | +| product_document_ids | One2many | product.document | Documentos | +| combo_ids | Many2many | product.combo | Combos | +| product_tag_ids | Many2many | product.tag | Etiquetas | + +#### Campos Computed Importantes + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| product_variant_count | Integer | Numero de variantes | +| product_variant_id | Many2one | Primera variante | +| is_product_variant | Boolean | False siempre | +| has_configurable_attributes | Boolean | Es configurable | +| product_document_count | Integer | Cantidad de documentos | +| volume_uom_name | Char | Etiqueta UoM volumen | +| weight_uom_name | Char | Etiqueta UoM peso | + +--- + +### 2.2 product.product (Variante de Producto) + +**Archivo:** `models/product_product.py` +**Descripcion:** Variante especifica de un producto +**Hereda de:** product.template (via _inherits) + +#### Campos Especificos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| product_tmpl_id | Many2one | Si | - | Plantilla padre | +| default_code | Char | No | - | Referencia interna | +| barcode | Char | No | - | Codigo de barras | +| standard_price | Float | No | 0 | Costo (company dependent) | +| volume | Float | No | 0 | Volumen | +| weight | Float | No | 0 | Peso | +| active | Boolean | No | True | Activa | +| combination_indices | Char | No | Computed | Indice de combinacion | + +#### Campos Computed + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| price_extra | Float | Precio extra de atributos | +| lst_price | Float | Precio de venta calculado | +| code | Char | Codigo contextual | +| partner_ref | Char | Referencia del cliente | +| is_product_variant | Boolean | True siempre | + +#### Campos Relacionales + +| Campo | Tipo | Relacion | Descripcion | +|-------|------|----------|-------------| +| product_tmpl_id | Many2one | product.template | Plantilla (req) | +| product_template_attribute_value_ids | Many2many | product.template.attribute.value | Combinacion | +| additional_product_tag_ids | Many2many | product.tag | Etiquetas adicionales | +| all_product_tag_ids | Many2many | product.tag | Todas las etiquetas | + +#### Campos de Imagen + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| image_variant_1920 | Image | Imagen especifica variante | +| image_variant_1024/512/256/128 | Image | Redimensiones | + +--- + +### 2.3 product.category (Categoria) + +**Archivo:** `models/product_category.py` +**Descripcion:** Categorias jerarquicas de productos +**Hereda de:** mail.thread + +#### Campos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char | Si | - | Nombre | +| complete_name | Char | No | Computed | Nombre completo | +| parent_id | Many2one | No | - | Categoria padre | +| parent_path | Char | No | - | Ruta en arbol | +| child_id | One2many | No | - | Subcategorias | +| product_count | Integer | No | Computed | Cantidad productos | +| product_properties_definition | Properties | No | - | Propiedades custom | + +--- + +### 2.4 product.pricelist (Lista de Precios) + +**Archivo:** `models/product_pricelist.py` +**Descripcion:** Listas de precios con reglas +**Hereda de:** mail.thread, mail.activity.mixin + +#### Campos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char | Si | - | Nombre | +| active | Boolean | No | True | Activa | +| sequence | Integer | No | 16 | Orden | +| currency_id | Many2one | Si | - | Moneda | +| company_id | Many2one | No | - | Empresa | +| country_group_ids | Many2many | No | - | Grupos de pais | +| item_ids | One2many | No | - | Reglas | + +--- + +### 2.5 product.pricelist.item (Regla de Precio) + +**Archivo:** `models/product_pricelist.py` +**Descripcion:** Reglas individuales de precios + +#### Campos de Aplicacion + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| pricelist_id | Many2one | Si | - | Lista de precios | +| applied_on | Selection | Si | 3_global | Nivel de aplicacion | +| categ_id | Many2one | No | - | Categoria | +| product_tmpl_id | Many2one | No | - | Producto | +| product_id | Many2one | No | - | Variante | +| min_quantity | Float | No | 0 | Cantidad minima | +| date_start | Date | No | - | Fecha inicio | +| date_end | Date | No | - | Fecha fin | + +#### Campos de Calculo + +| Campo | Tipo | Default | Descripcion | +|-------|------|---------|-------------| +| base | Selection | list_price | Base: list_price, standard_price, pricelist | +| compute_price | Selection | fixed | Tipo: percentage, formula, fixed | +| fixed_price | Float | 0 | Precio fijo | +| percent_price | Float | 0 | Porcentaje de descuento | +| price_discount | Float | 0 | Descuento en formula | +| price_round | Float | 0 | Redondeo | +| price_surcharge | Float | 0 | Recargo | +| price_min_margin | Float | 0 | Margen minimo | +| price_max_margin | Float | 0 | Margen maximo | +| base_pricelist_id | Many2one | - | Pricelist base (si base=pricelist) | + +--- + +### 2.6 product.attribute (Atributo) + +**Archivo:** `models/product_attribute.py` +**Descripcion:** Atributos de producto (Color, Talla) + +#### Campos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char | Si | - | Nombre | +| active | Boolean | No | True | Activo | +| create_variant | Selection | No | always | Modo: always, dynamic, no_variant | +| display_type | Selection | No | radio | Visualizacion | +| sequence | Integer | No | 20 | Orden | +| value_ids | One2many | No | - | Valores | +| attribute_line_ids | One2many | No | - | Lineas en plantillas | +| product_tmpl_ids | Many2many | No | Computed | Productos | +| number_related_products | Integer | No | Computed | Cantidad productos | + +--- + +### 2.7 product.attribute.value (Valor de Atributo) + +**Archivo:** `models/product_attribute.py` +**Descripcion:** Valores posibles de un atributo + +#### Campos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| name | Char | Si | - | Nombre | +| attribute_id | Many2one | Si | - | Atributo | +| sequence | Integer | No | 10 | Orden | +| default_extra_price | Float | No | 0 | Precio extra | +| is_custom | Boolean | No | False | Permite entrada libre | +| html_color | Char | No | - | Color HTML | +| color | Integer | No | 0 | Indice color | +| image | Binary | No | - | Imagen | +| active | Boolean | No | True | Activo | +| is_used_on_products | Boolean | No | Computed | En uso | + +--- + +### 2.8 product.supplierinfo (Info de Proveedor) + +**Archivo:** `models/product_supplierinfo.py` +**Descripcion:** Precios y plazos de proveedores + +#### Campos + +| Campo | Tipo | Req | Default | Descripcion | +|-------|------|-----|---------|-------------| +| partner_id | Many2one | Si | - | Proveedor | +| product_name | Char | No | - | Nombre en proveedor | +| product_code | Char | No | - | Codigo en proveedor | +| sequence | Integer | No | 1 | Prioridad | +| product_uom_id | Many2one | Si | Computed | UoM | +| min_qty | Float | Si | 0 | Cantidad minima | +| price | Float | No | 0 | Precio | +| price_discounted | Float | No | Computed | Precio con descuento | +| company_id | Many2one | No | - | Empresa | +| currency_id | Many2one | Si | - | Moneda | +| date_start | Date | No | - | Fecha inicio | +| date_end | Date | No | - | Fecha fin | +| product_id | Many2one | No | - | Variante | +| product_tmpl_id | Many2one | Si | - | Plantilla | +| delay | Integer | Si | 1 | Tiempo entrega (dias) | +| discount | Float | No | 0 | Descuento % | + +--- + +## 3. Diagrama Entidad-Relacion + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ CATALOGO │ +└─────────────────────────────────────────────────────────────────────────┘ + +┌───────────────────────┐ categ_id ┌───────────────────────┐ +│ product.category │◄──────────────────│ product.template │ +│ │ │ │ +│ - name │ parent_id │ - name │ +│ - complete_name │◄─────────────┐ │ - type │ +│ - parent_path │ │ │ - list_price │ +└───────────────────────┘ │ │ - standard_price │ + │ └───────────┬───────────┘ + │ │ + │ │ product_variant_ids + │ ▼ + │ ┌───────────────────────┐ + │ │ product.product │ + │ │ │ + │ │ - default_code │ + │ │ - barcode │ + │ │ - price_extra │ + │ └───────────────────────┘ + + +┌─────────────────────────────────────────────────────────────────────────┐ +│ ATRIBUTOS │ +└─────────────────────────────────────────────────────────────────────────┘ + +┌───────────────────────┐ attribute_id ┌───────────────────────┐ +│ product.attribute │◄──────────────────│ product.attribute.value│ +│ │ │ │ +│ - name │ value_ids │ - name │ +│ - create_variant │──────────────────►│ - default_extra_price│ +│ - display_type │ │ - html_color │ +└───────────┬───────────┘ └───────────────────────┘ + │ + │ attribute_line_ids + ▼ +┌───────────────────────────────────────────┐ +│ product.template.attribute.line │ +│ │ +│ - product_tmpl_id ──► product.template │ +│ - value_ids ──► product.attribute.value │ +└───────────────────────────────────────────┘ + + +┌─────────────────────────────────────────────────────────────────────────┐ +│ PRECIOS │ +└─────────────────────────────────────────────────────────────────────────┘ + +┌───────────────────────┐ item_ids ┌───────────────────────┐ +│ product.pricelist │──────────────────►│ product.pricelist.item│ +│ │ │ │ +│ - name │ pricelist_id │ - applied_on │ +│ - currency_id │◄──────────────────│ - base │ +│ - sequence │ │ - compute_price │ +└───────────────────────┘ │ - fixed_price │ + │ - percent_price │ + └───────────┬───────────┘ + │ + │ product_tmpl_id + │ product_id + │ categ_id + ▼ + ┌───────────────────────┐ + │ (Referencias a │ + │ product.template, │ + │ product.product, │ + │ product.category) │ + └───────────────────────┘ + + +┌─────────────────────────────────────────────────────────────────────────┐ +│ PROVEEDORES │ +└─────────────────────────────────────────────────────────────────────────┘ + +┌───────────────────────┐ seller_ids ┌───────────────────────┐ +│ product.template │──────────────────►│ product.supplierinfo │ +│ │ │ │ +│ (plantilla) │ product_tmpl_id │ - partner_id ──► │ +│ │◄──────────────────│ res.partner │ +└───────────────────────┘ │ - min_qty │ + │ - price │ + │ - delay │ + └───────────────────────┘ +``` + +--- + +## 4. Flujo de Creacion de Variantes + +``` +product.template.create() o write() + │ + ▼ +_create_variant_ids() + │ + ├── Obtener combinaciones posibles + │ (itertools.product de values) + │ + ├── Filtrar combinaciones invalidas + │ (exclusiones por atributo) + │ + ├── Para cada combinacion: + │ ├── Si existe y activa: mantener + │ ├── Si existe inactiva: activar + │ └── Si no existe: crear product.product + │ + └── Desactivar/eliminar obsoletas +``` + +--- + +## 5. Flujo de Calculo de Precio (Pricelist) + +``` +pricelist._compute_price_rule(producto, qty, uom, date) + │ + ├── Obtener reglas aplicables + │ (applied_on, dates, min_quantity) + │ + ├── Ordenar por prioridad: + │ 0_variant > 1_product > 2_category > 3_global + │ + └── Calcular con primera regla aplicable: + ├── base=list_price: producto.list_price + ├── base=standard_price: producto.standard_price + └── base=pricelist: recursivo en base_pricelist_id + + Luego aplicar: + - compute_price=fixed: fixed_price + - compute_price=percentage: price * (1 - percent/100) + - compute_price=formula: price - discount + surcharge + + redondeo + margenes +``` + +--- + +**Referencias:** +- Carpeta models: `addons/product/models/` +- Total archivos: 26 diff --git a/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-project.md b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-project.md new file mode 100644 index 000000000..a5a6aa66c --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-project.md @@ -0,0 +1,315 @@ +# Modelo de Datos: Project + +**Modulo:** project +**Total Modelos:** 6+ +**Modelos Documentados:** 5 (principales) + +--- + +## 1. Indice de Modelos Principales + +| Modelo | Descripcion | Lineas | Archivo | +|--------|-------------|--------|---------| +| project.task | Tareas | 2,212 | project_task.py | +| project.project | Proyectos | 1,443 | project_project.py | +| project.task.type | Etapas tareas | 239 | project_task_type.py | +| project.milestone | Hitos | 143 | project_milestone.py | +| project.project.stage | Etapas proyectos | 83 | project_project_stage.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 project.task (Tareas) + +**Archivo:** `models/project_task.py` +**Descripcion:** Task +**Hereda de:** portal.mixin, mail.thread.cc, mail.activity.mixin, rating.mixin, mail.tracking.duration.mixin + +#### Estados (state) + +| Estado | Nombre UI | Tipo | +|--------|-----------|------| +| 01_in_progress | In Progress | OPEN | +| 02_changes_requested | Changes Requested | OPEN | +| 03_approved | Approved | OPEN | +| 04_waiting_normal | Waiting | OPEN | +| 1_done | Done | CLOSED | +| 1_canceled | Cancelled | CLOSED | + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre tarea | +| active | Boolean | No | Activa | +| state | Selection | Si | Estado actual | +| stage_id | Many2one | No | Etapa kanban | +| priority | Selection | No | Prioridad (0/1) | +| sequence | Integer | No | Orden | +| is_closed | Boolean | No | Estado cerrado (computed) | + +#### Campos de Proyecto + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| project_id | Many2one | Proyecto contenedor | +| displayed_image_id | Many2one | Imagen destacada | +| partner_id | Many2one | Cliente | +| company_id | Many2one | Empresa | + +#### Campos de Asignacion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| user_ids | Many2many | Asignados | +| date_assign | Datetime | Fecha asignacion | + +#### Campos de Fechas + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| date_deadline | Datetime | Fecha limite | +| date_end | Datetime | Fecha fin | +| date_last_stage_update | Datetime | Ultima actualizacion etapa | + +#### Campos de Subtareas + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| parent_id | Many2one | Tarea padre | +| child_ids | One2many | Subtareas | +| subtask_count | Integer | Cantidad subtareas | + +#### Campos de Dependencias + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| depend_on_ids | Many2many | Bloqueada por | +| dependent_ids | Many2many | Bloquea a | + +#### Campos de Milestone + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| milestone_id | Many2one | Hito asociado | + +#### Campos de Recurrencia + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| recurring_task | Boolean | Es recurrente | +| recurrence_id | Many2one | Patron recurrencia | +| repeat_interval | Integer | Cada X periodos | +| repeat_unit | Selection | day/week/month/year | +| repeat_type | Selection | forever/until | +| repeat_until | Date | Fecha fin recurrencia | + +#### Campos Etapas Personales + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| personal_stage_type_ids | Many2many | Etapas del usuario | +| personal_stage_id | Many2one | Etapa personal actual | + +--- + +### 2.2 project.project (Proyectos) + +**Archivo:** `models/project_project.py` +**Descripcion:** Project +**Hereda de:** portal.mixin, mail.alias.mixin, rating.parent.mixin, mail.activity.mixin, analytic.plan.fields.mixin + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre proyecto | +| active | Boolean | No | Activo | +| sequence | Integer | No | Orden | +| partner_id | Many2one | No | Cliente | +| company_id | Many2one | Si | Empresa | +| user_id | Many2one | No | Project Manager | +| date_start | Date | No | Fecha inicio | +| date | Date | No | Fecha fin | + +#### Campos de Tareas + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| task_ids | One2many | Tareas del proyecto | +| task_count | Integer | Total tareas | +| open_task_count | Integer | Tareas abiertas | +| closed_task_count | Integer | Tareas cerradas | +| type_ids | Many2many | Etapas habilitadas | + +#### Campos de Visibilidad + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| privacy_visibility | Selection | Visibilidad | +| privacy_visibility_warning | Char | Advertencia | + +#### Campos Analiticos + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| account_id | Many2one | Cuenta analitica | + +#### Campos de Configuracion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| allow_subtasks | Boolean | Permitir subtareas | +| allow_milestones | Boolean | Permitir milestones | +| allow_task_dependencies | Boolean | Permitir dependencias | +| allow_recurring_tasks | Boolean | Permitir recurrencia | + +--- + +### 2.3 project.task.type (Etapas de Tareas) + +**Archivo:** `models/project_task_type.py` +**Descripcion:** Task Stage + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre etapa | +| sequence | Integer | No | Orden (default=1) | +| fold | Boolean | No | Etapa final/plegada | +| project_ids | Many2many | No | Proyectos asociados | +| user_id | Many2one | No | Propietario (personal) | +| rating_active | Boolean | No | Solicitar calificacion | +| rotting_threshold_days | Integer | No | Dias sin actualizar | +| mail_template_id | Many2one | No | Template email | + +--- + +### 2.4 project.milestone (Hitos) + +**Archivo:** `models/project_milestone.py` +**Descripcion:** Project Milestone + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre hito | +| deadline | Date | No | Fecha limite | +| is_reached | Boolean | No | Alcanzado | +| project_id | Many2one | Si | Proyecto | +| task_ids | One2many | No | Tareas vinculadas | +| task_count | Integer | No | Total tareas | +| done_task_count | Integer | No | Tareas completadas | + +--- + +### 2.5 project.project.stage (Etapas de Proyectos) + +**Archivo:** `models/project_project_stage.py` +**Descripcion:** Project Stage + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre etapa | +| sequence | Integer | No | Orden | +| fold | Boolean | No | Plegada | +| color | Integer | No | Color | +| mail_template_id | Many2one | No | Template notificacion | + +--- + +## 3. Diagrama Entidad-Relacion + +``` +┌───────────────────────┐ task_ids ┌───────────────────────┐ +│ project.project │─────────────────────►│ project.task │ +│ │ │ │ +│ - name │ project_id │ - name │ +│ - task_count │◄──────────────────────│ - state │ +│ - allow_subtasks │ │ - stage_id │ +│ - account_id │ │ - user_ids │ +└───────────┬───────────┘ └───────────┬───────────┘ + │ │ + │ type_ids parent_id, child_ids + ▼ │ +┌───────────────────────┐ ┌───────────┴───────────┐ +│ project.task.type │ │ project.task │ +│ │ │ (subtarea) │ +│ - fold │ └───────────────────────┘ +│ - sequence │ +│ - project_ids │ milestone_id ┌───────────────────────┐ +└───────────────────────┘ │ project.milestone │ + │ │ +┌───────────────────────┐◄──────────────────────│ - deadline │ +│ project.task │ │ - is_reached │ +│ │ │ - task_count │ +│ - milestone_id │ └───────────────────────┘ +└───────────────────────┘ + +DEPENDENCIAS: +┌───────────────────────┐ depend_on_ids ┌───────────────────────┐ +│ project.task │◄────────────────────►│ project.task │ +│ (bloqueada) │ dependent_ids │ (bloquea) │ +└───────────────────────┘ └───────────────────────┘ +``` + +--- + +## 4. Constantes del Modulo + +```python +# Estados cerrados +CLOSED_STATES = { + '1_done': 'Done', + '1_canceled': 'Cancelled', +} + +# Estados abiertos = todos excepto CLOSED_STATES + +# Campos leibles por portal +PROJECT_TASK_READABLE_FIELDS = { + 'id', 'active', 'priority', 'project_id', + 'user_ids', 'date_deadline', 'subtask_count', + 'milestone_id', 'stage_id', 'tag_ids', ... +} + +# Campos escribibles por portal +PROJECT_TASK_WRITABLE_FIELDS = { + 'name', 'description', 'partner_id', + 'date_deadline', 'stage_id', 'state', ... +} +``` + +--- + +## 5. Tabla Etapas Personales + +``` +project.task.stage.personal (tabla: project_task_user_rel) + +| task_id | user_id | stage_id | +|---------|---------|----------| +| 1 | 5 | 10 | +| 1 | 7 | 12 | + +Cada usuario puede tener diferente etapa para la misma tarea +``` + +--- + +## 6. Constraints (Restricciones) + +| Modelo | Campos | Validacion | Mensaje | +|--------|--------|------------|---------| +| project.task | company_id, partner_id | Tarea y partner deben ser misma empresa | Task and partner must be same company | +| project.task | child_ids, project_id | Tarea con subtareas no puede ser privada | Task with subtasks cannot be private | +| project.task | depend_on_ids | No dependencias ciclicas | Two tasks cannot depend on each other | +| project.task | parent_id | No jerarquia recursiva | Cannot create recursive hierarchy | +| project.task.type | user_id, project_ids | Etapa personal no puede vincularse a proyectos | Personal stage cannot link to projects | +| project.project | stage_id | Etapa debe ser misma empresa | Stage must be same company as project | +| project.task.recurrence | repeat_interval | Intervalo debe ser > 0 | Interval should be greater than 0 | +| project.task.recurrence | repeat_type, repeat_until | Fecha fin debe ser futura | End date should be in the future | + +--- + +**Referencias:** +- Carpeta models: `addons/project/models/` diff --git a/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-purchase.md b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-purchase.md new file mode 100644 index 000000000..e64f54c50 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-purchase.md @@ -0,0 +1,233 @@ +# Modelo de Datos: Purchase + +**Modulo:** purchase +**Total Modelos:** 8+ +**Modelos Documentados:** 3 (principales) + +--- + +## 1. Indice de Modelos Principales + +| Modelo | Descripcion | Campos | Archivo | +|--------|-------------|--------|---------| +| purchase.order | Orden de compra | 50+ | purchase_order.py | +| purchase.order.line | Linea de orden | 30+ | purchase_order_line.py | +| purchase.bill.line.match | Match facturas | 10+ | purchase_bill_line_match.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 purchase.order (Orden de Compra) + +**Archivo:** `models/purchase_order.py` +**Descripcion:** Purchase Order +**Hereda de:** portal.mixin, product.catalog.mixin, mail.thread, mail.activity.mixin + +#### Estado (state) + +| Estado | Nombre UI | Descripcion | +|--------|-----------|-------------| +| draft | RFQ | Solicitud de cotizacion | +| sent | RFQ Sent | Enviada al proveedor | +| to approve | To Approve | Pendiente aprobacion | +| purchase | Purchase Order | Confirmada | +| cancel | Cancelled | Cancelada | + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Referencia de orden | +| priority | Selection | No | Prioridad (0=Normal, 1=Urgente) | +| origin | Char | No | Documento origen | +| partner_ref | Char | No | Referencia del proveedor | +| date_order | Datetime | Si | Fecha limite de orden | +| date_approve | Datetime | No | Fecha de confirmacion | +| date_planned | Datetime | No | Fecha esperada llegada | +| state | Selection | Si | Estado | +| locked | Boolean | No | Orden bloqueada | + +#### Campos de Proveedor + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| partner_id | Many2one | Si | Proveedor | +| dest_address_id | Many2one | No | Direccion dropship | +| user_id | Many2one | No | Comprador responsable | + +#### Campos Monetarios + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| currency_id | Many2one | Moneda | +| amount_untaxed | Monetary | Subtotal (computed) | +| amount_tax | Monetary | Impuestos (computed) | +| amount_total | Monetary | Total (computed) | +| amount_total_cc | Monetary | Total en moneda empresa | + +#### Campos de Facturacion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| invoice_ids | Many2many | Facturas vinculadas | +| invoice_count | Integer | Cantidad de facturas | +| invoice_status | Selection | Estado facturacion | + +#### Campos de Lineas + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| order_line | One2many | Lineas de orden | + +#### Campos de Configuracion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| fiscal_position_id | Many2one | Posicion fiscal | +| payment_term_id | Many2one | Terminos de pago | +| incoterm_id | Many2one | Incoterm | +| company_id | Many2one | Empresa | +| acknowledged | Boolean | Acuse de recibo | + +--- + +### 2.2 purchase.order.line (Linea de Orden) + +**Archivo:** `models/purchase_order_line.py` +**Descripcion:** Purchase Order Line + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| order_id | Many2one | Si | Orden padre | +| sequence | Integer | No | Secuencia | +| product_id | Many2one | No | Producto | +| product_template_id | Many2one | No | Plantilla producto | +| name | Text | Si | Descripcion | +| date_planned | Datetime | Si | Fecha esperada | +| display_type | Selection | No | Tipo display (section/note) | + +#### Campos de Cantidad + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| product_qty | Float | Cantidad ordenada | +| product_uom | Many2one | Unidad de medida | +| qty_received | Float | Cantidad recibida | +| qty_received_manual | Float | Recibida manual | +| qty_invoiced | Float | Cantidad facturada | +| qty_to_invoice | Float | Pendiente facturar | + +#### Campos de Precio + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| price_unit | Float | Precio unitario | +| price_subtotal | Monetary | Subtotal (computed) | +| price_total | Monetary | Total con impuestos | +| price_tax | Float | Impuestos | +| discount | Float | Descuento % | + +#### Campos de Impuestos + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| taxes_id | Many2many | Impuestos aplicables | +| tax_calculation_rounding_method | Selection | Metodo redondeo | + +#### Campos de Facturacion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| invoice_lines | One2many | Lineas de factura | + +--- + +### 2.3 purchase.bill.line.match (Match de Facturas) + +**Archivo:** `models/purchase_bill_line_match.py` +**Descripcion:** Purchase Bill Line Match + +#### Campos + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| product_id | Many2one | Si | Producto | +| company_id | Many2one | Si | Empresa | +| partner_id | Many2one | Si | Proveedor | +| purchase_order_id | Many2one | No | Orden de compra | +| aml_id | Many2one | No | Linea de factura | +| line_qty | Float | No | Cantidad linea | +| line_amount_untaxed | Monetary | No | Monto sin impuesto | + +--- + +## 3. Diagrama Entidad-Relacion + +``` +┌───────────────────────┐ order_line ┌───────────────────────┐ +│ purchase.order │──────────────────────►│ purchase.order.line │ +│ │ │ │ +│ - name │ order_id │ - product_id │ +│ - state │◄──────────────────────│ - product_qty │ +│ - partner_id │ │ - price_unit │ +│ - date_order │ │ - qty_received │ +│ - amount_total │ │ - qty_invoiced │ +└───────────┬───────────┘ └───────────┬───────────┘ + │ │ + │ partner_id │ invoice_lines + ▼ ▼ +┌───────────────────────┐ ┌───────────────────────┐ +│ res.partner │ │ account.move.line │ +│ │ │ │ +│ (Proveedor) │ │ (Linea factura) │ +└───────────────────────┘ └───────────────────────┘ + +┌───────────────────────┐ invoice_ids ┌───────────────────────┐ +│ purchase.order │──────────────────────►│ account.move │ +│ │ │ │ +│ - invoice_status │ │ (Factura proveedor) │ +│ - invoice_count │ │ │ +└───────────────────────┘ └───────────────────────┘ +``` + +--- + +## 4. Extensiones a Otros Modelos + +### 4.1 res.partner (Proveedor) + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| property_purchase_currency_id | Many2one | Moneda compra | +| purchase_warn | Selection | Tipo advertencia | +| purchase_warn_msg | Text | Mensaje advertencia | +| supplier_invoice_count | Integer | Facturas proveedor | +| receipt_reminder_email | Boolean | Email recordatorio | +| reminder_date_before_receipt | Integer | Dias antes recepcion | + +### 4.2 product.template + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| purchase_ok | Boolean | Se puede comprar | +| purchase_method | Selection | Metodo control | +| purchase_line_warn | Selection | Advertencia | +| purchase_line_warn_msg | Text | Mensaje advertencia | + +--- + +## 5. Constraints (Restricciones) + +| Modelo | Campos | Validacion | Mensaje | +|--------|--------|------------|---------| +| purchase.order | company_id, order_line | Productos deben pertenecer a la misma empresa | Product belongs to different company | + +**Nota:** La mayoria de validaciones de negocio se realizan en los metodos de transicion (button_confirm, button_approve), no como constraints explicitos. + +--- + +**Referencias:** +- Carpeta models: `addons/purchase/models/` diff --git a/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-sale.md b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-sale.md new file mode 100644 index 000000000..c48326fc7 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-sale.md @@ -0,0 +1,264 @@ +# Modelo de Datos: Sale + +**Modulo:** sale +**Total Modelos:** 15+ +**Modelos Documentados:** 2 (principales) + +--- + +## 1. Indice de Modelos Principales + +| Modelo | Descripcion | Campos | Archivo | +|--------|-------------|--------|---------| +| sale.order | Orden de venta | 60+ | sale_order.py | +| sale.order.line | Linea de orden | 40+ | sale_order_line.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 sale.order (Orden de Venta) + +**Archivo:** `models/sale_order.py` +**Descripcion:** Sales Order +**Hereda de:** portal.mixin, product.catalog.mixin, mail.thread, mail.activity.mixin, utm.mixin + +#### Estado (state) + +| Estado | Nombre UI | Descripcion | +|--------|-----------|-------------| +| draft | Quotation | Cotizacion | +| sent | Quotation Sent | Enviada | +| sale | Sales Order | Confirmada | +| cancel | Cancelled | Cancelada | + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Referencia de orden | +| state | Selection | Si | Estado | +| locked | Boolean | No | Orden bloqueada | +| client_order_ref | Char | No | Referencia del cliente | +| create_date | Datetime | No | Fecha creacion | +| date_order | Datetime | Si | Fecha de orden | +| commitment_date | Datetime | No | Fecha entrega prometida | +| origin | Char | No | Documento origen | +| reference | Char | No | Referencia de pago | + +#### Campos de Partner + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| partner_id | Many2one | Si | Cliente | +| partner_invoice_id | Many2one | Si | Direccion facturacion | +| partner_shipping_id | Many2one | Si | Direccion envio | + +#### Campos de Firma/Pago Online + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| require_signature | Boolean | Requiere firma | +| require_payment | Boolean | Requiere pago | +| prepayment_percent | Float | Porcentaje anticipo | +| signature | Image | Firma digital | +| signed_by | Char | Nombre firmante | +| signed_on | Datetime | Fecha firma | + +#### Campos de Configuracion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| validity_date | Date | Fecha expiracion cotizacion | +| journal_id | Many2one | Diario facturacion | +| fiscal_position_id | Many2one | Posicion fiscal | +| payment_term_id | Many2one | Terminos de pago | +| pricelist_id | Many2one | Lista de precios | +| currency_id | Many2one | Moneda | +| user_id | Many2one | Vendedor | +| team_id | Many2one | Equipo de ventas | +| company_id | Many2one | Empresa | + +#### Campos Monetarios + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| amount_untaxed | Monetary | Subtotal (computed) | +| amount_tax | Monetary | Impuestos (computed) | +| amount_total | Monetary | Total (computed) | +| amount_to_invoice | Monetary | Pendiente facturar | +| amount_invoiced | Monetary | Ya facturado | + +#### Campos de Facturacion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| invoice_ids | Many2many | Facturas vinculadas | +| invoice_count | Integer | Cantidad facturas | +| invoice_status | Selection | Estado facturacion | + +#### Campos de Pago + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| transaction_ids | Many2many | Transacciones | +| authorized_transaction_ids | Many2many | Autorizadas | +| amount_paid | Float | Monto pagado | + +#### Campos de Lineas + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| order_line | One2many | Lineas de orden | +| note | Html | Terminos y condiciones | +| tag_ids | Many2many | Etiquetas | + +--- + +### 2.2 sale.order.line (Linea de Orden) + +**Archivo:** `models/sale_order_line.py` +**Descripcion:** Sales Order Line + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| order_id | Many2one | Si | Orden padre | +| sequence | Integer | No | Secuencia | +| company_id | Many2one | No | Empresa (related) | +| name | Text | Si | Descripcion | +| display_type | Selection | No | Tipo display (section/note/line_note) | +| state | Selection | No | Estado (related) | + +#### Campos de Producto + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| product_id | Many2one | Producto | +| product_template_id | Many2one | Plantilla producto | +| product_uom | Many2one | Unidad de medida | +| product_uom_qty | Float | Cantidad ordenada | +| product_custom_attribute_value_ids | One2many | Valores atributos custom | + +#### Campos de Cantidad + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| product_uom_qty | Float | Cantidad ordenada | +| qty_delivered | Float | Cantidad entregada | +| qty_delivered_method | Selection | Metodo calculo entrega | +| qty_invoiced | Float | Cantidad facturada | +| qty_to_invoice | Float | Pendiente facturar | +| untaxed_amount_invoiced | Float | Importe facturado sin imp | +| untaxed_amount_to_invoice | Float | Importe pendiente sin imp | + +#### Campos de Precio + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| price_unit | Float | Precio unitario | +| discount | Float | Descuento % | +| price_subtotal | Monetary | Subtotal (computed) | +| price_tax | Float | Impuestos | +| price_total | Monetary | Total con impuestos | +| price_reduce | Float | Precio con descuento | +| price_reduce_taxinc | Float | Precio con desc+imp | +| price_reduce_taxexcl | Float | Precio con desc sin imp | + +#### Campos de Impuestos + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| tax_id | Many2many | Impuestos aplicables | + +#### Campos de Facturacion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| invoice_lines | Many2many | Lineas de factura | +| invoice_status | Selection | Estado facturacion (computed) | +| is_downpayment | Boolean | Es anticipo | + +#### Campos de Vendedor + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| salesman_id | Many2one | Vendedor (related) | + +--- + +## 3. Diagrama Entidad-Relacion + +``` +┌───────────────────────┐ order_line ┌───────────────────────┐ +│ sale.order │──────────────────────►│ sale.order.line │ +│ │ │ │ +│ - name │ order_id │ - product_id │ +│ - state │◄──────────────────────│ - product_uom_qty │ +│ - partner_id │ │ - price_unit │ +│ - date_order │ │ - qty_delivered │ +│ - amount_total │ │ - qty_invoiced │ +└───────────┬───────────┘ └───────────┬───────────┘ + │ │ + │ partner_id │ invoice_lines + ▼ ▼ +┌───────────────────────┐ ┌───────────────────────┐ +│ res.partner │ │ account.move.line │ +│ │ │ │ +│ (Cliente) │ │ (Linea factura) │ +└───────────────────────┘ └───────────────────────┘ + +┌───────────────────────┐ invoice_ids ┌───────────────────────┐ +│ sale.order │──────────────────────►│ account.move │ +│ │ │ │ +│ - invoice_status │ │ (Factura cliente) │ +│ - invoice_count │ │ type = 'out_invoice' │ +└───────────────────────┘ └───────────────────────┘ + +┌───────────────────────┐ transaction_ids ┌───────────────────────┐ +│ sale.order │──────────────────────►│ payment.transaction │ +│ │ │ │ +│ - amount_paid │ │ (Transaccion pago) │ +│ - require_payment │ │ │ +└───────────────────────┘ └───────────────────────┘ +``` + +--- + +## 4. Constantes del Modulo + +```python +# Estados de facturacion +INVOICE_STATUS = [ + ('upselling', 'Upselling Opportunity'), + ('invoiced', 'Fully Invoiced'), + ('to invoice', 'To Invoice'), + ('no', 'Nothing to Invoice') +] + +# Estados de orden +SALE_ORDER_STATE = [ + ('draft', "Quotation"), + ('sent', "Quotation Sent"), + ('sale', "Sales Order"), + ('cancel', "Cancelled"), +] +``` + +--- + +## 6. Constraints (Restricciones) + +| Modelo | Campos | Validacion | Mensaje | +|--------|--------|------------|---------| +| sale.order | company_id, order_line | Productos deben pertenecer a la misma empresa | Product belongs to different company | +| sale.order | prepayment_percent | Porcentaje entre 0 y 100% si require_payment | Prepayment percentage must be valid | +| sale.order.line | combo_item_id | No debe setearse manualmente | Combo item cannot be set manually | +| product.template | company_id | No restringir a empresa si fue vendido en otra | Product sold in other company | + +--- + +**Referencias:** +- Carpeta models: `addons/sale/models/` +- Constantes: `addons/sale/const.py` diff --git a/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-stock.md b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-stock.md new file mode 100644 index 000000000..1a5fe305b --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/03-modelado-datos/MODELO-stock.md @@ -0,0 +1,388 @@ +# Modelo de Datos: Stock + +**Modulo:** stock +**Total Modelos:** 30+ +**Modelos Documentados:** 8 (principales) + +--- + +## 1. Indice de Modelos Principales + +| Modelo | Descripcion | Campos | Archivo | +|--------|-------------|--------|---------| +| stock.move | Movimientos | 40+ | stock_move.py | +| stock.move.line | Lineas operacion | 25+ | stock_move_line.py | +| stock.picking | Albaranes | 35+ | stock_picking.py | +| stock.location | Ubicaciones | 20+ | stock_location.py | +| stock.warehouse | Almacenes | 30+ | stock_warehouse.py | +| stock.quant | Cantidades | 20+ | stock_quant.py | +| stock.rule | Reglas procura | 15+ | stock_rule.py | +| stock.lot | Lotes/SN | 10+ | stock_lot.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 stock.move (Movimientos) + +**Archivo:** `models/stock_move.py` +**Descripcion:** Stock Move +**Hereda de:** - (modelo independiente) + +#### Estado (state) + +| Estado | Descripcion | +|--------|-------------| +| draft | Nuevo | +| waiting | Esperando otro movimiento | +| confirmed | Esperando disponibilidad | +| partially_available | Parcialmente disponible | +| assigned | Disponible/Reservado | +| done | Completado | +| cancel | Cancelado | + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| sequence | Integer | No | Secuencia (default=10) | +| state | Selection | Si | Estado del movimiento | +| date | Datetime | Si | Fecha programada | +| product_id | Many2one | Si | Producto | +| product_qty | Float | No | Cantidad real (computed) | +| product_uom_qty | Float | Si | Cantidad demandada | +| product_uom | Many2one | Si | Unidad de medida | +| location_id | Many2one | Si | Ubicacion origen | +| location_dest_id | Many2one | Si | Ubicacion destino | +| picking_id | Many2one | No | Albaran asociado | +| company_id | Many2one | Si | Empresa | +| partner_id | Many2one | No | Direccion destino | + +#### Campos de Cadena + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| move_orig_ids | Many2many | Movimientos origen (cadena) | +| move_dest_ids | Many2many | Movimientos destino (cadena) | +| rule_id | Many2one | Regla que lo creo | +| picking_type_id | Many2one | Tipo de operacion | +| route_ids | Many2many | Rutas preferidas | +| move_line_ids | One2many | Lineas de operacion | + +--- + +### 2.2 stock.move.line (Lineas de Operacion) + +**Archivo:** `models/stock_move_line.py` +**Descripcion:** Product Moves (Detailed Operations) + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| move_id | Many2one | No | Movimiento padre | +| picking_id | Many2one | No | Albaran | +| product_id | Many2one | Si | Producto | +| product_uom_id | Many2one | Si | Unidad de medida | +| quantity | Float | Si | Cantidad hecha | +| quantity_product_uom | Float | No | Cantidad en UoM producto | +| lot_id | Many2one | No | Lote/Numero de serie | +| lot_name | Char | No | Nombre del lote | +| package_id | Many2one | No | Paquete origen | +| result_package_id | Many2one | No | Paquete destino | +| owner_id | Many2one | No | Propietario | +| location_id | Many2one | Si | Ubicacion origen | +| location_dest_id | Many2one | Si | Ubicacion destino | +| state | Selection | No | Estado (related) | + +--- + +### 2.3 stock.picking (Albaranes/Transferencias) + +**Archivo:** `models/stock_picking.py` +**Descripcion:** Transfer +**Hereda de:** mail.thread, mail.activity.mixin + +#### Estado (state) + +| Estado | Descripcion | +|--------|-------------| +| draft | Borrador | +| waiting | Esperando otra operacion | +| confirmed | Esperando disponibilidad | +| assigned | Listo | +| done | Completado | +| cancel | Cancelado | + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | No | Referencia (secuencial) | +| state | Selection | No | Estado (computed) | +| picking_type_id | Many2one | Si | Tipo de operacion | +| picking_type_code | Selection | No | Codigo tipo (related) | +| move_type | Selection | Si | Politica envio | +| location_id | Many2one | Si | Ubicacion origen | +| location_dest_id | Many2one | Si | Ubicacion destino | +| move_ids | One2many | No | Movimientos | +| move_line_ids | One2many | No | Lineas detalladas | +| partner_id | Many2one | No | Contacto | +| company_id | Many2one | No | Empresa (related) | + +#### Campos de Fecha + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| scheduled_date | Datetime | Fecha programada (computed) | +| date_deadline | Datetime | Fecha limite | +| date_done | Datetime | Fecha de ejecucion | + +#### Campos Adicionales + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| priority | Selection | Prioridad (0=Normal, 1=Urgente) | +| user_id | Many2one | Responsable | +| owner_id | Many2one | Propietario asignado | +| is_locked | Boolean | Bloqueado (default=True) | +| backorder_id | Many2one | Albaran original (backorder) | +| signature | Image | Firma | +| printed | Boolean | Impreso | + +--- + +### 2.4 stock.location (Ubicaciones) + +**Archivo:** `models/stock_location.py` +**Descripcion:** Inventory Locations +**Estructura:** Jerarquica (_parent_store) + +#### Tipos de Ubicacion (usage) + +| Tipo | Descripcion | Virtual | +|------|-------------|---------| +| supplier | Proveedores | Si | +| view | Virtual/Jerarquica | Si | +| internal | Interna fisica | No | +| customer | Clientes | Si | +| inventory | Ajustes inventario | Si | +| production | Produccion | Si | +| transit | Transito | Si | + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre | +| complete_name | Char | No | Nombre completo (computed) | +| usage | Selection | Si | Tipo de ubicacion | +| location_id | Many2one | No | Ubicacion padre | +| child_ids | One2many | No | Ubicaciones hijas | +| parent_path | Char | No | Ruta de padres (indexed) | +| company_id | Many2one | No | Empresa | +| barcode | Char | No | Codigo de barras | +| active | Boolean | No | Activo | + +#### Campos de Configuracion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| replenish_location | Boolean | Trigger reposicion | +| removal_strategy_id | Many2one | Estrategia extraccion | +| putaway_rule_ids | One2many | Reglas ubicacion | +| storage_category_id | Many2one | Categoria almacenamiento | +| cyclic_inventory_frequency | Integer | Frecuencia inventario (dias) | + +--- + +### 2.5 stock.warehouse (Almacenes) + +**Archivo:** `models/stock_warehouse.py` +**Descripcion:** Warehouse + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre | +| code | Char | Si | Codigo corto (5 chars) | +| company_id | Many2one | Si | Empresa | +| partner_id | Many2one | No | Direccion | +| active | Boolean | No | Activo | +| sequence | Integer | No | Secuencia | + +#### Ubicaciones del Almacen + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| view_location_id | Many2one | Ubicacion raiz | +| lot_stock_id | Many2one | Stock principal | +| wh_input_stock_loc_id | Many2one | Entrada | +| wh_qc_stock_loc_id | Many2one | Control calidad | +| wh_output_stock_loc_id | Many2one | Salida | +| wh_pack_stock_loc_id | Many2one | Empaque | + +#### Configuracion de Flujos + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| reception_steps | Selection | Pasos recepcion (1/2/3) | +| delivery_steps | Selection | Pasos entrega (1/2/3) | +| route_ids | Many2many | Rutas por defecto | + +#### Tipos de Operacion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| in_type_id | Many2one | Tipo recepcion | +| out_type_id | Many2one | Tipo envio | +| int_type_id | Many2one | Tipo interno | +| pick_type_id | Many2one | Tipo pick | +| pack_type_id | Many2one | Tipo empaque | + +--- + +### 2.6 stock.quant (Cantidades) + +**Archivo:** `models/stock_quant.py` +**Descripcion:** Quants + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| product_id | Many2one | Si | Producto | +| product_tmpl_id | Many2one | No | Plantilla (related) | +| location_id | Many2one | Si | Ubicacion | +| warehouse_id | Many2one | No | Almacen (related) | +| lot_id | Many2one | No | Lote/SN | +| package_id | Many2one | No | Paquete | +| owner_id | Many2one | No | Propietario | +| company_id | Many2one | No | Empresa (related) | + +#### Campos de Cantidad + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| quantity | Float | Cantidad actual (readonly) | +| reserved_quantity | Float | Cantidad reservada (readonly) | +| available_quantity | Float | Cantidad disponible (computed) | +| in_date | Datetime | Fecha de entrada | + +#### Campos de Inventario + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| inventory_quantity | Float | Cantidad contada | +| inventory_date | Date | Fecha programada | +| inventory_diff_quantity | Float | Diferencia (computed) | +| last_count_date | Date | Ultimo conteo | +| user_id | Many2one | Usuario asignado | + +--- + +### 2.7 stock.rule (Reglas de Procura) + +**Archivo:** `models/stock_rule.py` +**Descripcion:** Stock Rule + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Nombre | +| active | Boolean | No | Activo | +| action | Selection | Si | Accion (pull/push/pull_push) | +| sequence | Integer | No | Secuencia | +| route_id | Many2one | Si | Ruta | +| location_src_id | Many2one | No | Ubicacion origen | +| location_dest_id | Many2one | Si | Ubicacion destino | +| picking_type_id | Many2one | Si | Tipo operacion | +| procure_method | Selection | Si | Metodo procura | +| company_id | Many2one | No | Empresa | +| warehouse_id | Many2one | No | Almacen | +| delay | Integer | No | Tiempo entrega (dias) | +| auto | Selection | No | Automatizacion | +| propagate_cancel | Boolean | No | Propagar cancelacion | + +--- + +### 2.8 stock.lot (Lotes/Numeros de Serie) + +**Archivo:** `models/stock_lot.py` +**Descripcion:** Lot/Serial + +#### Campos Principales + +| Campo | Tipo | Req | Descripcion | +|-------|------|-----|-------------| +| name | Char | Si | Lote/SN | +| ref | Char | No | Referencia interna | +| product_id | Many2one | Si | Producto | +| product_qty | Float | No | Cantidad en mano (computed) | +| company_id | Many2one | Si | Empresa | +| expiration_date | Datetime | No | Fecha expiracion | +| use_date | Datetime | No | Mejor antes de | +| removal_date | Datetime | No | Fecha remocion | +| alert_date | Datetime | No | Fecha alerta | +| quant_ids | One2many | No | Cantidades | + +--- + +## 3. Diagrama Entidad-Relacion + +``` +┌───────────────────────┐ move_ids ┌───────────────────────┐ +│ stock.picking │────────────────────►│ stock.move │ +│ │ │ │ +│ - name │ picking_id │ - state │ +│ - state │◄─────────────────────│ - product_id │ +│ - picking_type_id │ │ - product_qty │ +│ - scheduled_date │ │ - location_id │ +└───────────┬───────────┘ │ - location_dest_id │ + │ └───────────┬───────────┘ + │ picking_type_id │ + ▼ move_line_ids +┌───────────────────────┐ ▼ +│ stock.picking.type │ ┌───────────────────────┐ +│ │ │ stock.move.line │ +│ - code │ │ │ +│ - warehouse_id │ │ - quantity │ +│ - reservation_method │ │ - lot_id │ +└───────────────────────┘ │ - package_id │ + └───────────────────────┘ + +┌───────────────────────┐ lot_stock_id ┌───────────────────────┐ +│ stock.warehouse │────────────────────►│ stock.location │ +│ │ │ │ +│ - code │ warehouse_id │ - usage │ +│ - reception_steps │◄─────────────────────│ - location_id │ +│ - delivery_steps │ │ - parent_path │ +└───────────────────────┘ └───────────┬───────────┘ + │ + location_id │ + ▼ +┌───────────────────────┐ ┌───────────────────────┐ +│ stock.quant │────────────────────►│ product.product │ +│ │ product_id │ │ +│ - quantity │ │ (from product) │ +│ - reserved_quantity │ └───────────────────────┘ +│ - location_id │ +│ - lot_id │ +└───────────────────────┘ + +┌───────────────────────┐ route_id ┌───────────────────────┐ +│ stock.rule │────────────────────►│ stock.route │ +│ │ │ │ +│ - action │ │ - name │ +│ - procure_method │ │ - product_selectable │ +│ - location_src_id │ │ - warehouse_selectable│ +│ - location_dest_id │ └───────────────────────┘ +└───────────────────────┘ +``` + +--- + +**Referencias:** +- Carpeta models: `addons/stock/models/` diff --git a/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-account.md b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-account.md new file mode 100644 index 000000000..457081318 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-account.md @@ -0,0 +1,230 @@ +# Flujo de Negocio: Account (Contabilidad) + +**Modulo:** account +**Modelo Principal:** account.move +**Aplica Workflow:** Si + +--- + +## 1. Estados del Documento (state) + +| Estado | Nombre UI | Descripcion | Siguiente | +|--------|-----------|-------------|-----------| +| draft | Borrador | Estado inicial, editable | posted | +| posted | Publicado | Contabilizado, no editable | cancel | +| cancel | Cancelado | Anulado | - | + +--- + +## 2. Diagrama de Transiciones - account.move + +``` + ┌──────────────────┐ + │ DRAFT │ + │ (Borrador) │ + └────────┬─────────┘ + │ + action_post() + _post() + │ + ▼ + ┌──────────────────┐ + │ POSTED │ + │ (Publicado) │◄────────┐ + └────────┬─────────┘ │ + │ │ + ┌──────────────────┼───────────────────┤ + │ │ │ + ▼ ▼ │ +┌─────────────────┐ ┌──────────────┐ button_draft() +│ button_cancel() │ │action_reverse│ (si draftable) +│ │ │ │ +└────────┬────────┘ └──────┬───────┘ + │ │ + ▼ ▼ +┌─────────────────┐ ┌──────────────────┐ +│ CANCEL │ │ NUEVO MOVE │ +│ (Cancelado) │ │ (tipo inverso) │ +└─────────────────┘ └──────────────────┘ +``` + +--- + +## 3. Estados de Pago (payment_state) + +``` + ┌──────────────┐ + │ NOT_PAID │ (Publicado, sin pagos) + └──────┬───────┘ + │ Reconciliacion parcial + ▼ + ┌──────────────┐ + │ PARTIAL │ (Parcialmente pagado) + └──────┬───────┘ + │ Pago registrado pero no confirmado + ▼ + ┌──────────────┐ + │ IN_PAYMENT │ (En proceso) + └──────┬───────┘ + │ Pago completado + ▼ + ┌──────────────┐ + │ PAID │ (Totalmente pagado) + └──────────────┘ + +Estados especiales: + ┌──────────────┐ + │ REVERSED │ (Asiento revertido) + └──────────────┘ + + ┌──────────────┐ + │ BLOCKED │ (Pagos bloqueados manualmente) + └──────────────┘ +``` + +--- + +## 4. Flujo de Factura de Cliente + +``` +1. CREACION + └─ account.move creado con move_type='out_invoice' + └─ state = 'draft' + └─ payment_state = N/A + +2. AGREGAR LINEAS + └─ invoice_line_ids con productos, cantidades, impuestos + └─ amount_untaxed, amount_tax, amount_total calculados + +3. PUBLICAR (action_post) + └─ Validar: lineas balanceadas (debito = credito) + └─ Generar nombre secuencial + └─ state = 'posted' + └─ payment_state = 'not_paid' + +4. REGISTRAR PAGO + └─ Crear account.payment vinculado + └─ Reconciliar lineas receivable + └─ payment_state = 'partial' o 'paid' + +5. (Opcional) REVERTIR + └─ action_reverse() crea nota de credito + └─ Ambos documentos se reconcilian + └─ payment_state = 'reversed' +``` + +--- + +## 5. Flujo de Pago (account.payment) + +``` + ┌──────────────────┐ + │ DRAFT │ + │ (Borrador) │ + └────────┬─────────┘ + │ + action_post() + │ + ▼ + ┌──────────────────┐ + │ IN_PROCESS │ (Opcional, segun metodo) + │ (En proceso) │ + └────────┬─────────┘ + │ + action_validate() + │ + ▼ + ┌──────────────────┐ + │ PAID │ + │ (Pagado) │ + └────────┬─────────┘ + │ + ┌───┴───┐ + │ │ + ▼ ▼ +┌────────────┐ ┌──────────────┐ +│ CANCELED │ │ REJECTED │ +│(Cancelado) │ │ (Rechazado) │ +└────────────┘ └──────────────┘ +``` + +--- + +## 6. Metodos de Transicion + +| Modelo | Metodo | De Estado | A Estado | +|--------|--------|-----------|----------| +| account.move | action_post() | draft | posted | +| account.move | _post() | draft | posted | +| account.move | button_draft() | posted | draft | +| account.move | button_cancel() | * | cancel | +| account.move | action_reverse() | posted | (crea nuevo) | +| account.payment | action_post() | draft | in_process/paid | +| account.payment | action_validate() | in_process | paid | +| account.payment | action_cancel() | * | canceled | +| account.payment | action_reject() | * | rejected | +| account.payment | action_draft() | canceled | draft | + +--- + +## 7. Reglas de Negocio + +| ID | Regla | Validacion | Mensaje | +|----|-------|------------|---------| +| R1 | Balance cero | sum(debit) = sum(credit) | El asiento no cuadra | +| R2 | Cuenta requerida | line.account_id presente | Falta cuenta contable | +| R3 | Periodo cerrado | date dentro de periodo abierto | Periodo cerrado | +| R4 | Diario requerido | journal_id presente | Falta diario | +| R5 | Secuencia unica | name unico por empresa | Secuencia duplicada | +| R6 | Hash integridad | hash no modificado | Violacion de integridad | + +--- + +## 8. Acciones Automaticas + +| Trigger | Accion | Condicion | +|---------|--------|-----------| +| action_post | Generar nombre secuencial | Si name = '/' | +| action_post | Calcular hash | Si journal.restrict_mode_hash_table | +| Reconciliacion | Actualizar payment_state | Automatico | +| Pago creado | Vincular con facturas | Si invoice_ids | + +--- + +## 9. Permisos por Estado + +| Estado | Editable | Grupos | +|--------|----------|--------| +| draft | Si | account.group_account_invoice | +| posted | No | account.group_account_manager (para revertir) | +| cancel | No | - | + +--- + +## 10. Flujo de Reconciliacion + +``` +FACTURA (out_invoice, posted) + │ + │ Linea receivable: credit = 1000 + │ + ▼ +PAGO (payment, paid) + │ + │ Linea receivable: debit = 1000 + │ + ▼ +RECONCILIACION + │ + ├─► account.partial.reconcile (si parcial) + │ amount = 500 + │ + └─► account.full.reconcile (si completo) + Agrupa todas las partidas +``` + +--- + +**Referencias:** +- account_move.py: action_post, button_draft, button_cancel +- account_payment.py: action_post, action_validate diff --git a/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-base.md b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-base.md new file mode 100644 index 000000000..61fb9508e --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-base.md @@ -0,0 +1,405 @@ +# Flujo de Negocio: Base (Kernel) + +**Modulo:** base +**Aplica Workflow:** Parcial (no tiene estados tradicionales, pero tiene flujos importantes) + +--- + +## 1. Nota sobre Workflows en Base + +El modulo base no implementa workflows de estados tradicionales como otros modulos +(sale, purchase, stock). Sin embargo, tiene flujos de negocio criticos: + +1. **Autenticacion de usuarios** +2. **Control de acceso por reglas** +3. **Ciclo de tareas programadas (cron)** +4. **Jerarquia de permisos y grupos** + +--- + +## 2. Flujo de Autenticacion (res.users) + +### 2.1 Diagrama de Autenticacion + +``` + ┌─────────────────────────────────────────────────────────────────────┐ + │ SOLICITUD DE AUTENTICACION │ + │ (login + password + context) │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ VERIFICAR COOLDOWN │ + │ - Consultar intentos fallidos recientes │ + │ - Si > 5 fallos en ultimos 60s: RECHAZAR │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ BUSCAR USUARIO │ + │ - Buscar por login │ + │ - Verificar usuario activo │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ┌─────────────┴─────────────┐ + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ USUARIO EXISTE │ │ NO EXISTE │ + │ Y ACTIVO │ │ O INACTIVO │ + └────────┬────────┘ └────────┬────────┘ + │ │ + ▼ ▼ + ┌─────────────────────────────────┐ ┌─────────────────┐ + │ VALIDAR CREDENCIALES │ │ ACCESS DENIED │ + │ - Comparar hash password │ │ + Log intento │ + │ - pbkdf2_sha512 (600k rounds) │ └─────────────────┘ + └─────────────────────────────────┘ + │ + ┌─────────────┴─────────────┐ + │ │ + ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ +│ PASSWORD OK │ │ PASSWORD FAIL │ +└────────┬────────┘ └────────┬────────┘ + │ │ + ▼ ▼ +┌─────────────────────────┐ ┌─────────────────────────┐ +│ ACTUALIZAR SESSION │ │ REGISTRAR FALLO │ +│ - Generar session_token │ │ - Incrementar contador │ +│ - Registrar login_date │ │ - Verificar cooldown │ +│ - Actualizar log │ └────────────┬────────────┘ +└────────────┬────────────┘ │ + │ ▼ + ▼ ┌─────────────────┐ + ┌─────────────────┐ │ ACCESS DENIED │ + │ AUTH SUCCESS │ └─────────────────┘ + │ (uid, method) │ + └─────────────────┘ +``` + +### 2.2 Configuracion Anti-Brute-Force + +| Parametro | Valor Default | Descripcion | +|-----------|---------------|-------------| +| Intentos maximos | 5 | Antes de activar cooldown | +| Tiempo cooldown | 60 segundos | Espera despues de exceder intentos | +| Configurable via | ir.config.parameter | auth_login_cooldown_* | + +--- + +## 3. Flujo de Control de Acceso (ir.rule) + +### 3.1 Diagrama de Evaluacion de Reglas + +``` + ┌─────────────────────────────────────────────────────────────────────┐ + │ SOLICITUD DE ACCESO A REGISTRO │ + │ (modelo, registro, modo: read/write/create/unlink) │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ BUSCAR REGLAS APLICABLES │ + │ - Filtrar por modelo │ + │ - Filtrar por modo (perm_read, perm_write, etc.) │ + │ - Filtrar activas │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ SEPARAR REGLAS │ + │ - GLOBALES: sin grupos asignados │ + │ - POR GRUPO: con grupos asignados │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ┌─────────────┴─────────────┐ + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ EVALUAR GLOBALES│ │ EVALUAR GRUPO │ + │ (AND) │ │ (OR) │ + │ │ │ │ + │ Todas deben │ │ Al menos una │ + │ cumplirse │ │ debe cumplirse │ + └────────┬────────┘ └────────┬────────┘ + │ │ + └───────────┬───────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ COMBINAR RESULTADOS │ + │ FINAL = GLOBALES AND GRUPO │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ┌─────────────┴─────────────┐ + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ ACCESO PERMITIDO│ │ ACCESO DENEGADO │ + └─────────────────┘ └────────┬────────┘ + │ + ▼ + ┌─────────────────────┐ + │ AccessError │ + │ - Regla que falla │ + │ - Registro afectado │ + └─────────────────────┘ +``` + +### 3.2 Ejemplo de Evaluacion + +``` +Usuario: user_sales (grupo: sales_team.group_sale_salesman) + +Reglas para model sale.order: + - R1 (global): domain = [('company_id','in',company_ids)] + - R2 (group: group_sale_salesman): domain = [('user_id','=',user.id)] + - R3 (group: group_sale_manager): domain = [] (sin restriccion) + +Evaluacion para user_sales: + 1. GLOBALES: R1 debe cumplirse + 2. GRUPO: R2 OR R3 - pero user_sales solo tiene group_sale_salesman + Entonces solo R2 aplica + 3. FINAL: R1 AND R2 + = [('company_id','in',company_ids)] AND [('user_id','=',user.id)] +``` + +--- + +## 4. Flujo de Tareas Programadas (ir.cron) + +### 4.1 Diagrama de Ciclo de Cron + +``` + ┌─────────────────────────────────────────────────────────────────────┐ + │ ESTADO INICIAL │ + │ active=True, nextcall=DateTime, failure_count=0 │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ SISTEMA DETECTA │ + │ nextcall <= ahora │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ ADQUIRIR JOB │ + │ - Lock en BD (FOR UPDATE SKIP LOCKED) │ + │ - Marcar como en ejecucion │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ EJECUTAR │ + │ - Nuevo contexto con user_id │ + │ - Ejecutar ir.actions.server │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ┌─────────────┴─────────────┐ + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ EXITO │ │ FALLO │ + │ │ │ │ + │ lastcall = ahora│ │ failure_count++ │ + │ failure_count=0 │ │ first_failure │ + │ first_failure= │ │ = ahora │ + │ None │ │ (si primero) │ + └────────┬────────┘ └────────┬────────┘ + │ │ + │ ▼ + │ ┌─────────────────────────────────┐ + │ │ VERIFICAR DESACTIVACION │ + │ │ Si failure_count >= 5 │ + │ │ AND (ahora - first_failure) >= 7d│ + │ └─────────────┬───────────────────┘ + │ │ + │ ┌─────────────┴─────────────┐ + │ │ │ + │ ▼ ▼ + │ ┌─────────────────┐ ┌─────────────────┐ + │ │ NO DESACTIVAR │ │ DESACTIVAR │ + │ │ (< 5 fallos o │ │ active = False │ + │ │ < 7 dias) │ └────────┬────────┘ + │ └────────┬────────┘ │ + │ │ │ + └────────────┼───────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ CALCULAR SIGUIENTE EJECUCION │ + │ nextcall = lastcall + relativedelta(interval) │ + │ Minimo: 5 horas entre intentos si hay fallos │ + └─────────────────────────────────────────────────────────────────────┘ +``` + +### 4.2 Tabla de Estados de Cron + +| Estado | active | failure_count | first_failure_date | Descripcion | +|--------|--------|---------------|-------------------|-------------| +| Normal | True | 0 | None | Funcionando correctamente | +| Fallo temporal | True | 1-4 | Set | Reintentos pendientes | +| Desactivado | False | >= 5 | >= 7 dias | Requiere intervencion manual | + +### 4.3 Intervalo de Ejecucion + +| Tipo | Valor | +|------|-------| +| minutes | Cada X minutos | +| hours | Cada X horas | +| days | Cada X dias | +| weeks | Cada X semanas | +| months | Cada X meses | + +--- + +## 5. Flujo de Jerarquia de Grupos (res.groups) + +### 5.1 Diagrama de Implicaciones + +``` + ┌─────────────────────────────────────────────────────────────────────┐ + │ USUARIO ASIGNADO A GRUPO │ + │ user.group_ids = [group_A] │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ RESOLVER IMPLICACIONES │ + │ Si group_A.implied_ids = [group_B, group_C] │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ RESOLVER TRANSITIVAMENTE │ + │ Si group_B.implied_ids = [group_D] │ + │ Entonces: A → B → D (A implica D) │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ GRUPOS EFECTIVOS │ + │ user.all_group_ids = [group_A, group_B, group_C, group_D] │ + └─────────────────────────────────────────────────────────────────────┘ + + Ejemplo visual: + + group_sale_manager + │ + ├── implied_ids ──► group_sale_salesman + │ │ + │ └── implied_ids ──► group_sale_salesman_all_leads + │ + └── implied_ids ──► group_user + + Usuario con group_sale_manager tiene: + - group_sale_manager (explicito) + - group_sale_salesman (implicado) + - group_sale_salesman_all_leads (transitivo) + - group_user (implicado) +``` + +### 5.2 Grupos Excluyentes (Disjoint) + +``` + ┌──────────────────┐ DISJOINT ┌──────────────────┐ + │ group_user │◄────────────────►│ group_portal │ + │ (Interno) │ │ (Portal) │ + └────────┬─────────┘ └─────────┬────────┘ + │ DISJOINT │ + └──────────────────┬─────────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ group_public │ + │ (Publico) │ + └──────────────────┘ + + REGLA: Un usuario solo puede pertenecer a UNO de estos grupos. + - Si es group_user: usuario interno (empleado) + - Si es group_portal: usuario externo (cliente/proveedor) + - Si es group_public: usuario anonimo +``` + +--- + +## 6. Flujo de Sincronizacion Partner-Usuario + +### 6.1 Diagrama de Sincronizacion + +``` + ┌─────────────────────────────────────────────────────────────────────┐ + │ CAMBIO EN RES.PARTNER │ + │ Campos: name, email, phone, active, etc. │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ PROPAGAR A RES.USERS │ + │ Si partner tiene usuarios vinculados (user_ids) │ + │ Entonces actualizar campos relacionados en usuario │ + └─────────────────────────────────────────────────────────────────────┘ + + Y + + ┌─────────────────────────────────────────────────────────────────────┐ + │ CAMBIO EN RES.USERS │ + │ Campos: name, email, active, etc. │ + └─────────────────────────────────┬───────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────────────┐ + │ PROPAGAR A RES.PARTNER │ + │ Actualizar partner_id con nuevos valores │ + └─────────────────────────────────────────────────────────────────────┘ + + RESTRICCION: + - Si partner.active = False y tiene usuarios internos activos + → Error: no se puede archivar +``` + +--- + +## 7. Reglas de Negocio + +| ID | Regla | Validacion | Mensaje Error | +|----|-------|------------|---------------| +| R1 | Usuario admin no eliminable | res.users.unlink | No se puede eliminar admin | +| R2 | Partner con usuarios no archivable | res.partner.write | Partner tiene usuarios activos | +| R3 | Grupos excluyentes | res.users._check_disjoint | Usuario en grupos disjuntos | +| R4 | Empresa no puede cambiar padre | res.company.write | No se puede cambiar jerarquia | +| R5 | Secuencia step != 0 | ir.sequence.create | Step no puede ser cero | +| R6 | Cron requiere accion | ir.cron.create | Debe tener accion servidor | + +--- + +## 8. Acciones Automaticas + +| Trigger | Accion | Condicion | +|---------|--------|-----------| +| res.users.create | Crear partner | Si no existe partner_id | +| res.users.write(active=False) | Archivar partner | Si no tiene otros usuarios activos | +| res.company.create | Crear partner | Automatico | +| ir.cron.nextcall reached | Ejecutar accion | active=True | +| res.currency > 1 active | Activar grupo multimoneda | Automatico | + +--- + +## 9. Permisos por Modelo + +| Modelo | Grupo Minimo | Notas | +|--------|--------------|-------| +| res.partner | group_user | Lectura para portal | +| res.users | group_system | Solo admin | +| res.company | group_user | Lectura propia empresa | +| res.groups | group_system | Solo admin | +| ir.rule | group_system | Solo admin | +| ir.cron | group_system | Solo admin | + +--- + +**Referencias:** +- Archivo principal: `models/res_users.py`, `models/ir_rule.py`, `models/ir_cron.py` +- Seguridad: `security/base_security.xml` diff --git a/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-crm.md b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-crm.md new file mode 100644 index 000000000..f1d20f907 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-crm.md @@ -0,0 +1,317 @@ +# Flujo de Negocio: CRM + +**Modulo:** crm +**Modelo Principal:** crm.lead +**Aplica Workflow:** Si + +--- + +## 1. Tipos de Registro + +| Tipo | Descripcion | Uso | +|------|-------------|-----| +| lead | Lead | Pre-calificado, investigacion | +| opportunity | Opportunity | Calificado, proceso de venta | + +--- + +## 2. Estados de Oportunidad (won_status) + +| Estado | Descripcion | Condicion | +|--------|-------------|-----------| +| pending | Pendiente | Cualquier caso no cerrado | +| won | Ganada | probability=100 AND stage.is_won=True | +| lost | Perdida | active=False AND probability=0 | + +--- + +## 3. Diagrama de Flujo Lead → Opportunity + +``` + ┌──────────────────┐ + │ CREAR LEAD │ + │ type='lead' │ + │ stage='New' │ + └────────┬─────────┘ + │ + [Investigacion/Calificacion] + │ + ▼ + ┌──────────────────┐ + │ CONVERTIR A │ + │ OPPORTUNITY │ + └────────┬─────────┘ + │ + convert_opportunity() + │ + ▼ + ┌──────────────────┐ + │ OPPORTUNITY │ + │ type='opportunity'│ + │ date_conversion │ + └────────┬─────────┘ + │ + ┌─────────────────┼─────────────────┐ + │ │ │ + ▼ ▼ ▼ + [Negociacion] [Propuesta] [Cierre] + │ │ │ + └─────────────────┼─────────────────┘ + │ + ┌──────────────────┴──────────────────┐ + │ │ + ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ +│ GANADA │ │ PERDIDA │ +│ action_set_won()│ │ action_set_lost()│ +│ │ │ │ +│ - probability=100│ │ - probability=0 │ +│ - stage.is_won │ │ - active=False │ +│ - won_status=won │ │ - won_status=lost│ +│ - date_closed │ │ - lost_reason_id │ +└──────────────────┘ └──────────────────┘ +``` + +--- + +## 4. Pipeline de Etapas + +``` +ETAPAS POR DEFECTO: + +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +│ New │──►│ Qualified│──►│Proposition│──►│ Won │ +│ seq=1 │ │ seq=2 │ │ seq=3 │ │ seq=70 │ +│ │ │ │ │ │ │ is_won ✓ │ +└──────────┘ └──────────┘ └──────────┘ └──────────┘ + │ + probability = 100 + won_status = 'won' +``` + +--- + +## 5. Metodos de Transicion + +### action_set_won() + +``` +PROPOSITO: Marcar oportunidad como ganada + +PROCESO: +1. Desarchiva lead (si estaba archivada) +2. Agrupa leads por etapa ganadora +3. Para cada grupo: + - Busca etapa con is_won=True + - Selecciona mejor etapa (sequence mas cercana) +4. Escribe stage_id y probability=100 + +EFECTOS: +- probability = 100 +- stage_id = etapa ganadora +- won_status = 'won' (computed) +- date_closed = ahora +``` + +### action_set_lost(**additional_values) + +``` +PROPOSITO: Marcar oportunidad como perdida + +PROCESO: +1. Archiva la lead (active=False) +2. Escribe additional_values (ej: lost_reason_id) +3. Establece probability=0, automated_probability=0 + +EFECTOS: +- active = False +- probability = 0 +- won_status = 'lost' (computed) +- date_closed = ahora +- lost_reason_id = razon +``` + +### convert_opportunity(partner, user_ids, team_id) + +``` +PROPOSITO: Convertir lead a opportunity + +PROCESO: +1. Para cada lead activa no ganada: + - Prepara datos de conversion + - Escribe cambios +2. Asigna vendedores (round-robin) + +DATOS CONVERSION: +- type = 'opportunity' +- date_conversion = ahora +- partner_id = cliente +- stage_id = etapa inicial +``` + +### merge_opportunity(user_id, team_id, auto_unlink) + +``` +PROPOSITO: Fusionar oportunidades duplicadas + +PROCESO: +1. Ordena por nivel de confianza +2. La primera es el resultado +3. Fusiona datos de las otras +4. Transfiere dependencias +5. Elimina registros mergeados + +CRITERIOS ORDENAMIENTO: +- No archivadas > archivadas +- Opportunity > lead +- Mayor sequence en stage +- Mayor probability +- Mayor ID +``` + +--- + +## 6. Predictive Lead Scoring (PLS) + +``` +CALCULO DE PROBABILIDAD: + +1. RECOPILAR FRECUENCIAS + └─ crm.lead.scoring.frequency + └─ Por cada (variable, value): + - won_count + - lost_count + +2. CALCULAR PROBABILIDADES BASE + P(Won) = total_won / total + P(Lost) = total_lost / total + +3. PARA CADA CAMPO A, B, C: + P(A|Won) = (won_count_A + 0.1) / (total_won + 0.1*n) + P(A|Lost) = (lost_count_A + 0.1) / (total_lost + 0.1*n) + +4. NAIVE BAYES: + S(Won) = P(A|Won) * P(B|Won) * P(C|Won) * P(Won) + S(Lost) = P(A|Lost) * P(B|Lost) * P(C|Lost) * P(Lost) + +5. RESULTADO: + automated_probability = S(Won) / (S(Won) + S(Lost)) * 100 +``` + +--- + +## 7. Flujo de Probabilidades + +``` +LEAD CREADA +│ +├─ probability = 0 (inicial) +│ +▼ +CAMBIOS EN CAMPOS PLS +│ +├─ _compute_probabilities() +├─ automated_probability recalculada +│ +├─ Si is_automated_probability == True: +│ └─ probability = automated_probability +│ +├─ Si is_automated_probability == False: +│ └─ probability mantiene valor manual +│ +▼ +CIERRE +│ +├─ action_set_won(): +│ └─ probability = 100 (forzado) +│ +└─ action_set_lost(): + └─ probability = 0 (forzado) +``` + +--- + +## 8. Reglas de Negocio + +| ID | Regla | Validacion | Mensaje | +|----|-------|------------|---------| +| R1 | Probability range | 0 <= probability <= 100 | Probability invalida | +| R2 | Won stage check | is_won implica probability=100 | Inconsistencia estado | +| R3 | Lead requires team | Si use_leads en team | Equipo sin leads | +| R4 | Partner on convert | partner_id al convertir | Cliente requerido | + +--- + +## 9. Acciones Automaticas + +| Trigger | Accion | Condicion | +|---------|--------|-----------| +| stage_id change | Recalcular PLS | Siempre | +| Campo PLS change | Recalcular automated_prob | Siempre | +| action_set_won | Actualizar frecuencias | Para PLS | +| action_set_lost | Actualizar frecuencias | Para PLS | +| Asignacion user_id | date_open = ahora | Primera asignacion | +| Cierre | date_closed = ahora | won o lost | + +--- + +## 10. Asignacion Automatica de Leads + +``` +CRON: ir_cron_crm_lead_assign + +PROCESO: +1. Buscar leads sin asignar +2. Por cada equipo con assignment_enabled: + - Filtrar por assignment_domain +3. Por cada miembro: + - Verificar assignment_optout = False + - Verificar capacidad (assignment_max) + - Aplicar assignment_domain_preferred +4. Distribuir round-robin + +EJEMPLO ROUND-ROBIN: +4 vendedores (S1-S4) para 6 leads (L1-L6): + S1 → L1, L5 + S2 → L2, L6 + S3 → L3 + S4 → L4 +``` + +--- + +## 11. Deteccion de Duplicados + +``` +CRITERIOS: +- Email normalizado exacto +- Phone sanitized exacto +- Mismo partner_id + +METODO: _get_lead_duplicates(partner, email, include_lost) + +USO: +- En wizard de conversion +- Para sugerir merge +- Filtro: activas y pendientes (o todas si include_lost) +``` + +--- + +## 12. Permisos por Estado + +| Accion | Grupo Requerido | +|--------|-----------------| +| Crear lead | group_sale_salesman | +| Convertir a opp | group_sale_salesman | +| Marcar ganada | group_sale_salesman | +| Marcar perdida | group_sale_salesman | +| Merge leads | group_sale_salesman | +| Config equipo | group_sale_manager | + +--- + +**Referencias:** +- crm_lead.py: action_set_won, action_set_lost, convert_opportunity +- crm_stage.py: etapas y is_won +- crm_lead_scoring_frequency.py: PLS diff --git a/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-project.md b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-project.md new file mode 100644 index 000000000..d7d42b378 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-project.md @@ -0,0 +1,315 @@ +# Flujo de Negocio: Project (Proyectos) + +**Modulo:** project +**Modelo Principal:** project.task +**Aplica Workflow:** Si (basado en state + stage_id) + +--- + +## 1. Estados de Tarea (state) + +| Estado | Nombre UI | Tipo | Descripcion | +|--------|-----------|------|-------------| +| 01_in_progress | In Progress | OPEN | Trabajo activo | +| 02_changes_requested | Changes Requested | OPEN | Requiere modificaciones | +| 03_approved | Approved | OPEN | Aprobada | +| 04_waiting_normal | Waiting | OPEN | Bloqueada por dependencias | +| 1_done | Done | CLOSED | Completada | +| 1_canceled | Cancelled | CLOSED | Cancelada | + +--- + +## 2. Diagrama de Estados + +``` + ┌──────────────────┐ + │ CREAR TAREA │ + │ state=in_progress│ + └────────┬─────────┘ + │ + ┌──────────────────┼──────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌──────────────────┐ ┌──────────────┐ ┌──────────────────┐ +│ IN_PROGRESS │ │ WAITING │ │ APPROVED │ +│ 01_in_progress │ │04_waiting_ │ │ 03_approved │ +│ │ │ normal │ │ │ +└────────┬─────────┘ └──────┬───────┘ └────────┬─────────┘ + │ │ │ + │ ┌──────────────┘ │ + │ │ (dependencias resueltas) │ + │ ▼ │ + │ ┌─────────────────┐ │ + │ │CHANGES_REQUESTED│ │ + │ │02_changes_ │ │ + │ │ requested │ │ + │ └────────┬────────┘ │ + │ │ │ + └───────────┼───────────────────────────┘ + │ + ┌──────────┴──────────┐ + │ │ + ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ +│ DONE │ │ CANCELED │ +│ 1_done │ │ 1_canceled │ +│ (is_closed=True)│ │ (is_closed=True)│ +└──────────────────┘ └──────────────────┘ +``` + +--- + +## 3. Etapas Kanban (stage_id) + +``` +ETAPAS POR PROYECTO: + +project.type_ids = [Stage1, Stage2, Stage3, ...] + +ASIGNACION: +- Al crear tarea: stage_id = primera etapa del proyecto +- Al mover en kanban: stage_id actualizado +- stage.fold = True indica etapa final + +ETAPA PERSONAL: +- Cada usuario puede tener diferente etapa +- personal_stage_id computed por usuario +- Sincronizado via project.task.stage.personal +``` + +--- + +## 4. Flujo de Dependencias + +``` +TAREA A depende de TAREA B: + +A.depend_on_ids = [B] +B.dependent_ids = [A] + +LOGICA _compute_state: +┌─────────────────────────────────────────────────┐ +│ Para cada tarea: │ +│ Si state en CLOSED_STATES: │ +│ → mantener (ya cerrada) │ +│ Si depend_on_ids tiene tareas no cerradas: │ +│ → state = '04_waiting_normal' │ +│ Si no: │ +│ → mantener state actual │ +└─────────────────────────────────────────────────┘ + +EFECTO: +- Tarea A automaticamente en "Waiting" +- Al cerrar B, A sale de "Waiting" +``` + +--- + +## 5. Flujo de Subtareas + +``` +ESTRUCTURA: +Tarea Padre +├── Subtarea 1 (child_ids) +│ └── Subtarea 1.1 +├── Subtarea 2 +└── Subtarea 3 + +HERENCIA: +- Subtareas heredan project_id del padre +- Subtareas pueden heredar milestone_id +- Tags se copian del padre al crear + +CONTEO: +subtask_count = len(child_ids) +``` + +--- + +## 6. Flujo de Recurrencia + +``` +CONFIGURACION: +┌─────────────────────────────────────────────────┐ +│ recurring_task = True │ +│ repeat_interval = 1 │ +│ repeat_unit = 'week' │ +│ repeat_type = 'forever' | 'until' │ +│ repeat_until = Date (si type='until') │ +└─────────────────────────────────────────────────┘ + +AL CERRAR TAREA: +1. _inverse_state detecta cierre +2. Si recurring_task y recurrence_id: + - Crea siguiente ocurrencia + - Nueva tarea con fecha + repeat_interval +3. Nueva tarea en estado inicial +``` + +--- + +## 7. Flujo de Milestones + +``` +ESTRUCTURA: +project.milestone +├── deadline: Fecha objetivo +├── is_reached: Marcado como alcanzado +└── task_ids: Tareas vinculadas + +ASIGNACION: +- Tarea.milestone_id = hito seleccionado +- Subtareas heredan milestone del padre +- Validacion: milestone debe estar en mismo proyecto + +TRACKING: +- task_count: Total tareas en hito +- done_task_count: Tareas cerradas +- Progreso = done_task_count / task_count +``` + +--- + +## 8. Metodos de Transicion + +### _compute_state() + +```python +@api.depends('stage_id', 'depend_on_ids.state') +def _compute_state(self): + for task in self: + # No cambiar si ya cerrada + if task.state in CLOSED_STATES: + continue + + # Verificar dependencias bloqueadas + has_blocking = any( + dep.state not in CLOSED_STATES + for dep in task.depend_on_ids + ) + + if has_blocking: + task.state = '04_waiting_normal' +``` + +### _inverse_state() + +```python +def _inverse_state(self): + # Maneja recurrencia al cerrar + for task in self: + if task.state in CLOSED_STATES and task.recurring_task: + task._create_next_recurrence() +``` + +### update_date_end(stage_id) + +```python +def update_date_end(self, stage_id): + stage = self.env['project.task.type'].browse(stage_id) + if stage.fold: # Etapa final + self.write({'date_end': fields.Datetime.now()}) + else: + self.write({'date_end': False}) +``` + +### stage_find(section_id, domain, order) + +```python +def stage_find(self, section_id, domain=[], order='sequence, id'): + # Buscar etapa valida para el proyecto + # Considera etapas del proyecto o por defecto + return stage_id +``` + +--- + +## 9. Reglas de Negocio + +| ID | Regla | Validacion | Mensaje | +|----|-------|------------|---------| +| R1 | Proyecto requerido | project_id presente | Seleccione proyecto | +| R2 | Milestone mismo proyecto | milestone.project_id = project_id | Milestone invalido | +| R3 | No auto-dependencia | task not in depend_on_ids | No puede depender de si misma | +| R4 | No ciclo dependencias | Validar DAG | Dependencia circular | + +--- + +## 10. Acciones Automaticas + +| Trigger | Accion | Condicion | +|---------|--------|-----------| +| stage_id change | update_date_end | Si stage.fold = True | +| stage_id change | date_last_stage_update | Siempre | +| user_ids change | date_assign | Primera asignacion | +| Cierre tarea recurrente | Crear siguiente | Si recurring_task | +| depend_on_ids change | Recalcular state | Siempre | + +--- + +## 11. Permisos por Portal + +### Campos Leibles + +```python +PROJECT_TASK_READABLE_FIELDS = { + 'id', 'active', 'priority', 'project_id', + 'user_ids', 'date_deadline', 'date_assign', + 'subtask_count', 'milestone_id', 'stage_id', + 'tag_ids', 'partner_id', 'display_name', + 'recurring_task', ... +} +``` + +### Campos Escribibles + +```python +PROJECT_TASK_WRITABLE_FIELDS = { + 'name', 'description', 'partner_id', + 'date_deadline', 'tag_ids', 'sequence', + 'stage_id', 'child_ids', 'parent_id', + 'priority', 'state', 'is_closed', ... +} +``` + +--- + +## 12. Flujo Completo de Tarea + +``` +1. CREAR TAREA + └─ state = '01_in_progress' + └─ stage_id = primera etapa proyecto + └─ date_create = ahora + +2. ASIGNACION + └─ user_ids asignados + └─ date_assign = ahora (primera vez) + +3. TRABAJO + └─ Cambios de stage_id (kanban) + └─ date_last_stage_update actualizado + └─ Si tiene dependencias → puede ir a 'waiting' + +4. REVISION (opcional) + └─ state = '02_changes_requested' + └─ state = '03_approved' + +5. CIERRE + └─ state = '1_done' o '1_canceled' + └─ is_closed = True + └─ date_end = ahora (si stage.fold) + └─ Si recurrente → crea siguiente + +6. RECURRENCIA (si aplica) + └─ Nueva tarea creada + └─ Fechas ajustadas segun interval + └─ state = inicial +``` + +--- + +**Referencias:** +- project_task.py: _compute_state, _inverse_state, update_date_end +- project_task_type.py: fold, sequence +- project_milestone.py: is_reached, task_count diff --git a/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-purchase.md b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-purchase.md new file mode 100644 index 000000000..0558dbbca --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-purchase.md @@ -0,0 +1,284 @@ +# Flujo de Negocio: Purchase (Compras) + +**Modulo:** purchase +**Modelo Principal:** purchase.order +**Aplica Workflow:** Si + +--- + +## 1. Estados de Orden de Compra (state) + +| Estado | Nombre UI | Descripcion | Siguiente | +|--------|-----------|-------------|-----------| +| draft | RFQ | Solicitud de cotizacion inicial | sent, to approve, purchase | +| sent | RFQ Sent | RFQ enviada al proveedor | to approve, purchase | +| to approve | To Approve | Pendiente de aprobacion | purchase, cancel | +| purchase | Purchase Order | Orden confirmada | (cancel via unlock) | +| cancel | Cancelled | Orden cancelada | draft | + +--- + +## 2. Diagrama de Transiciones + +``` + ┌──────────────────┐ + │ DRAFT │ + │ (RFQ) │ + └────────┬─────────┘ + │ + ┌──────────────────┼──────────────────┐ + │ │ │ + action_rfq_send() button_confirm() button_confirm() + │ (sin aprobacion) (con aprobacion) + ▼ │ │ +┌──────────────────┐ │ │ +│ SENT │ │ │ +│ (RFQ Sent) │ │ │ +└────────┬─────────┘ │ │ + │ │ │ + │ button_confirm() │ │ + │ │ │ + └───────────────────┼──────────────────┤ + │ │ + ▼ ▼ + (monto < limite) (monto >= limite) + │ │ + │ ┌────────┴────────┐ + │ │ TO APPROVE │ + │ │ (Pendiente) │ + │ └────────┬────────┘ + │ │ + │ button_approve() + │ │ + └────────┬─────────┘ + │ + ▼ + ┌──────────────────────────────┐ + │ PURCHASE │ + │ (Purchase Order) │ + │ │ + │ - Genera recepciones (stock)│ + │ - Habilita facturacion │ + │ - locked = True (opcional) │ + └──────────────┬───────────────┘ + │ + button_cancel() + (requiere unlock) + │ + ▼ + ┌──────────────────┐ + │ CANCEL │ + │ (Cancelled) │ + └────────┬─────────┘ + │ + button_draft() + │ + ▼ + ┌──────────────────┐ + │ DRAFT │ + │ (Reabierta) │ + └──────────────────┘ +``` + +--- + +## 3. Flujo Completo de Compra + +``` +1. CREACION (draft) + └─ Usuario crea purchase.order + └─ Selecciona proveedor (partner_id) + └─ Agrega lineas con productos y cantidades + └─ state = 'draft' + +2. ENVIO RFQ (opcional) + └─ action_rfq_send() + └─ Envia correo al proveedor + └─ state = 'sent' + +3. CONFIRMACION + └─ button_confirm() llamado + + 3a. SIN DOBLE VALIDACION: + └─ _approval_allowed() = True + └─ button_approve() ejecutado automatico + └─ state = 'purchase' + + 3b. CON DOBLE VALIDACION (monto > limite): + └─ _approval_allowed() = False + └─ state = 'to approve' + └─ Requiere gerente + +4. APROBACION (si aplica) + └─ button_approve() + └─ Verifica permisos + └─ state = 'purchase' + └─ date_approve = now() + +5. RECEPCION (integracion stock) + └─ Genera stock.picking automaticamente + └─ Operario recibe productos + └─ qty_received actualizado en lineas + +6. FACTURACION + └─ Proveedor envia factura + └─ Vincula factura con PO + └─ qty_invoiced actualizado + └─ invoice_status = 'invoiced' +``` + +--- + +## 4. Metodos de Transicion + +| Metodo | De Estado | A Estado | Descripcion | +|--------|-----------|----------|-------------| +| button_confirm() | draft/sent | to approve/purchase | Confirma orden | +| button_approve() | to approve | purchase | Aprueba orden | +| button_cancel() | * (no purchase locked) | cancel | Cancela | +| button_draft() | cancel | draft | Vuelve a borrador | +| action_rfq_send() | draft | sent | Envia RFQ | +| button_unlock() | purchase | purchase (unlocked) | Desbloquea | + +--- + +## 5. Reglas de Negocio + +| ID | Regla | Validacion | Mensaje | +|----|-------|------------|---------| +| R1 | Proveedor requerido | partner_id presente | Proveedor requerido | +| R2 | Lineas requeridas | order_line no vacio | Agregue al menos una linea | +| R3 | Producto valido | product_id empresa compatible | Producto de otra empresa | +| R4 | Cantidad positiva | product_qty > 0 | Cantidad debe ser positiva | +| R5 | Aprobacion limite | amount_total vs limite | Requiere aprobacion | + +--- + +## 6. Flujo de Doble Validacion + +``` +CONFIGURACION (res.config.settings) +│ +├─ po_lock = 'lock' | 'edit' +├─ po_double_validation = 'one_step' | 'two_step' +└─ po_double_validation_amount = monto_limite +│ +│ +EVALUACION EN button_confirm() +│ +├─ _approval_allowed(): +│ │ +│ ├─ Si po_double_validation == 'one_step': +│ │ └─ return True (sin aprobacion) +│ │ +│ └─ Si po_double_validation == 'two_step': +│ └─ return amount_total < limite +│ OR user tiene permiso manager +│ +└─ Resultado: + ├─ True → state = 'purchase' + └─ False → state = 'to approve' +``` + +--- + +## 7. Integracion con Stock + +``` +CONFIRMACION (state → purchase) +│ +├─ Genera stock.picking automaticamente +│ └─ picking_type_id = Recepciones +│ └─ partner_id = Proveedor +│ └─ location_dest_id = Stock +│ +├─ Por cada purchase.order.line: +│ └─ Crea stock.move +│ └─ product_id +│ └─ product_uom_qty = product_qty +│ └─ purchase_line_id = line.id +│ +RECEPCION (en stock) +│ +├─ Operario valida picking +├─ stock.move → done +└─ purchase.order.line.qty_received actualizado +``` + +--- + +## 8. Integracion con Facturacion + +``` +CREACION DE FACTURA +│ +├─ Desde PO: action_create_invoice() +│ └─ Crea account.move tipo 'in_invoice' +│ └─ Vincula lineas de factura con POL +│ +├─ Desde Factura entrante: +│ └─ purchase.bill.line.match +│ └─ Matching automatico o manual +│ +ACTUALIZACION +│ +├─ qty_invoiced calculado de lineas factura +├─ invoice_status recalculado: +│ ├─ 'no' = nada que facturar +│ ├─ 'to invoice' = pendiente +│ └─ 'invoiced' = todo facturado +``` + +--- + +## 9. Acciones Automaticas + +| Trigger | Accion | Condicion | +|---------|--------|-----------| +| button_confirm | Generar nombre secuencial | Si name = 'New' | +| button_approve | Establecer date_approve | Siempre | +| button_approve | Crear recepciones stock | Si integracion activa | +| Linea modificada | Recalcular totales | Siempre | +| Recepcion stock | Actualizar qty_received | Automatico | + +--- + +## 10. Permisos por Estado + +| Estado | Editable | Grupos | +|--------|----------|--------| +| draft | Si | purchase.group_purchase_user | +| sent | Si | purchase.group_purchase_user | +| to approve | Parcial | purchase.group_purchase_manager | +| purchase | No (locked) | purchase.group_purchase_manager (unlock) | +| cancel | No | purchase.group_purchase_user (reopen) | + +--- + +## 11. Flujo de Estados de Facturacion + +``` +PO CONFIRMADA (state = purchase) +│ +├─ invoice_status = 'no' +│ └─ Nada recibido aun +│ +├─ Recepcion parcial: +│ └─ qty_received > 0 +│ └─ invoice_status = 'to invoice' +│ +├─ Factura parcial: +│ └─ qty_invoiced < qty_received +│ └─ invoice_status = 'to invoice' +│ +└─ Todo facturado: + └─ qty_invoiced >= qty_received + └─ invoice_status = 'invoiced' +``` + +--- + +**Referencias:** +- purchase_order.py: button_confirm, button_approve, button_cancel +- Integracion stock: purchase_stock/ +- Integracion account: account_move.py diff --git a/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-sale.md b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-sale.md new file mode 100644 index 000000000..3cbed16dc --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-sale.md @@ -0,0 +1,306 @@ +# Flujo de Negocio: Sale (Ventas) + +**Modulo:** sale +**Modelo Principal:** sale.order +**Aplica Workflow:** Si + +--- + +## 1. Estados de Orden de Venta (state) + +| Estado | Nombre UI | Descripcion | Siguiente | +|--------|-----------|-------------|-----------| +| draft | Quotation | Cotizacion inicial | sent, sale | +| sent | Quotation Sent | Cotizacion enviada al cliente | sale, cancel | +| sale | Sales Order | Orden confirmada | cancel | +| cancel | Cancelled | Orden cancelada | draft | + +--- + +## 2. Diagrama de Transiciones + +``` + ┌──────────────────┐ + │ DRAFT │ + │ (Quotation) │ + └────────┬─────────┘ + │ + ┌──────────────────┼──────────────────┐ + │ │ │ +action_quotation_send() │ action_confirm() + │ │ │ + ▼ │ │ +┌──────────────────┐ │ │ +│ SENT │ │ │ +│ (Quotation Sent) │ │ │ +└────────┬─────────┘ │ │ + │ │ │ + │ action_confirm() │ │ + │ (firma/pago opt) │ │ + │ │ │ + └───────────────────┴──────────────────┘ + │ + ▼ + ┌───────────────────────────────────────┐ + │ │ + │ SALE │ + │ (Sales Order) │ + │ │ + │ - Genera entregas (sale_stock) │ + │ - Habilita facturacion │ + │ - locked = True (opcional) │ + │ │ + └───────────────────┬───────────────────┘ + │ + action_cancel() + (requiere unlock) + │ + ▼ + ┌──────────────────┐ + │ CANCEL │ + │ (Cancelled) │ + └────────┬─────────┘ + │ + action_draft() + │ + ▼ + ┌──────────────────┐ + │ DRAFT │ + │ (Reabierta) │ + └──────────────────┘ +``` + +--- + +## 3. Flujo Completo de Venta + +``` +1. CREACION (draft) + └─ Usuario crea sale.order + └─ Selecciona cliente (partner_id) + └─ Sistema calcula direcciones (invoice/shipping) + └─ Agrega lineas con productos + └─ state = 'draft' + +2. ENVIO COTIZACION (opcional) + └─ action_quotation_send() + └─ Envia correo al cliente + └─ state = 'sent' + └─ Cliente puede ver en portal + +3. VALIDACION PRE-CONFIRMACION (si configurado) + + 3a. FIRMA ONLINE (require_signature = True): + └─ Cliente firma en portal + └─ signature, signed_by, signed_on guardados + + 3b. PAGO ONLINE (require_payment = True): + └─ Cliente paga anticipo (prepayment_percent) + └─ payment.transaction creada + └─ amount_paid actualizado + +4. CONFIRMACION + └─ action_confirm() + └─ _action_confirm() ejecutado + └─ state = 'sale' + └─ date_order = now() (si era draft) + └─ name genera secuencia si 'New' + +5. ENTREGA (integracion sale_stock) + └─ Genera stock.picking automaticamente + └─ Operario prepara y envia + └─ qty_delivered actualizado en lineas + +6. FACTURACION + └─ _create_invoices() + └─ account.move creado tipo 'out_invoice' + └─ invoice_status actualizado +``` + +--- + +## 4. Metodos de Transicion + +| Metodo | De Estado | A Estado | Descripcion | +|--------|-----------|----------|-------------| +| action_confirm() | draft/sent | sale | Confirma orden | +| action_cancel() | * | cancel | Cancela orden | +| action_draft() | cancel | draft | Vuelve a borrador | +| action_quotation_send() | draft | sent | Envia cotizacion | +| action_lock() | sale | sale (locked) | Bloquea | +| action_unlock() | sale (locked) | sale | Desbloquea | + +--- + +## 5. Reglas de Negocio + +| ID | Regla | Validacion | Mensaje | +|----|-------|------------|---------| +| R1 | Cliente requerido | partner_id presente | Cliente requerido | +| R2 | Lineas requeridas | order_line no vacio | Agregue productos | +| R3 | Fecha confirmacion | date_order si state='sale' | Fecha requerida | +| R4 | Productos activos | Sin productos archivados | Producto archivado | +| R5 | Firma requerida | signature si require_signature | Firma pendiente | +| R6 | Pago requerido | amount_paid >= prepayment si require_payment | Pago pendiente | + +--- + +## 6. Flujo de Firma y Pago Online + +``` +COTIZACION ENVIADA (state = sent) +│ +├─ require_signature = True? +│ │ +│ ├─ SI: Cliente debe firmar en portal +│ │ └─ /my/orders//accept +│ │ └─ POST con firma en base64 +│ │ └─ sale.order.signature = firma +│ │ +│ └─ NO: Continua sin firma +│ +├─ require_payment = True? +│ │ +│ ├─ SI: Cliente debe pagar anticipo +│ │ └─ /my/orders//transaction +│ │ └─ prepayment_percent del total +│ │ └─ payment.transaction creada +│ │ └─ Estado: done/authorized +│ │ +│ └─ NO: Continua sin pago +│ +└─ Condiciones cumplidas: + └─ action_confirm() disponible + └─ O confirmacion automatica +``` + +--- + +## 7. Integracion con Inventario (sale_stock) + +``` +CONFIRMACION (state → sale) +│ +├─ sale_stock genera stock.picking +│ └─ picking_type_id = Envios +│ └─ partner_id = Cliente +│ └─ location_dest_id = Customer Location +│ +├─ Por cada sale.order.line: +│ └─ Crea stock.move +│ └─ product_id +│ └─ product_uom_qty +│ └─ sale_line_id = line.id +│ +ENVIO (en stock) +│ +├─ Operario prepara picking +├─ stock.move → done +└─ sale.order.line.qty_delivered actualizado +``` + +--- + +## 8. Flujo de Facturacion + +``` +CREAR FACTURA +│ +├─ Wizard: sale.advance.payment.inv +│ │ +│ ├─ advance_payment_method = 'delivered': +│ │ └─ Factura por cantidades entregadas +│ │ └─ qty_to_invoice = qty_delivered - qty_invoiced +│ │ +│ ├─ advance_payment_method = 'percentage': +│ │ └─ Factura anticipo (% del total) +│ │ └─ Linea tipo downpayment +│ │ +│ └─ advance_payment_method = 'fixed': +│ └─ Factura anticipo (monto fijo) +│ └─ Linea tipo downpayment +│ +├─ _create_invoices() +│ └─ account.move tipo 'out_invoice' +│ └─ Lineas vinculadas a sale.order.line +│ +ACTUALIZACION ESTADO +│ +├─ invoice_status recalculado: +│ ├─ 'no' = nada que facturar +│ ├─ 'to invoice' = pendiente +│ ├─ 'invoiced' = todo facturado +│ └─ 'upselling' = oportunidad upselling +``` + +--- + +## 9. Acciones Automaticas + +| Trigger | Accion | Condicion | +|---------|--------|-----------| +| action_confirm | Generar nombre secuencial | Si name = 'New' | +| action_confirm | Establecer date_order | Si era draft | +| action_confirm | Crear entregas | Si sale_stock instalado | +| Linea modificada | Recalcular totales | Siempre | +| Entrega completada | Actualizar qty_delivered | Automatico | +| Pago autorizado | Confirmar orden | Si autoconfirm | + +--- + +## 10. Permisos por Estado + +| Estado | Editable | Grupos | +|--------|----------|--------| +| draft | Si | sales_team.group_sale_salesman | +| sent | Si | sales_team.group_sale_salesman | +| sale | No (locked) | sales_team.group_sale_manager (unlock) | +| cancel | No | sales_team.group_sale_salesman (reopen) | + +--- + +## 11. Flujo de Estados de Facturacion + +``` +SO CONFIRMADA (state = sale) +│ +├─ invoice_status = 'no' +│ └─ Nada entregado aun +│ └─ O productos sin factura (service invoice='manual') +│ +├─ Entrega realizada: +│ └─ qty_delivered > 0 +│ └─ invoice_status = 'to invoice' +│ +├─ Upselling detectado: +│ └─ qty_delivered > qty_invoiced +│ └─ Politica = 'order' +│ └─ invoice_status = 'upselling' +│ +├─ Factura parcial: +│ └─ qty_invoiced < qty_delivered +│ └─ invoice_status = 'to invoice' +│ +└─ Todo facturado: + └─ qty_invoiced >= qty_to_invoice + └─ invoice_status = 'invoiced' +``` + +--- + +## 12. Constraint de Base de Datos + +```sql +-- Orden confirmada requiere fecha +CHECK( + (state = 'sale' AND date_order IS NOT NULL) + OR state != 'sale' +) +``` + +--- + +**Referencias:** +- sale_order.py: action_confirm, action_cancel, _create_invoices +- Integracion stock: sale_stock/ +- Wizard factura: sale_make_invoice_advance.py diff --git a/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-stock.md b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-stock.md new file mode 100644 index 000000000..80f2fa874 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/04-logica-negocio/FLUJO-stock.md @@ -0,0 +1,372 @@ +# Flujo de Negocio: Stock (Inventario) + +**Modulo:** stock +**Modelo Principal:** stock.picking, stock.move +**Aplica Workflow:** Si + +--- + +## 1. Estados del Movimiento (stock.move.state) + +| Estado | Nombre UI | Descripcion | Siguiente | +|--------|-----------|-------------|-----------| +| draft | Nuevo | Estado inicial, no confirmado | confirmed | +| waiting | Esperando | Esperando otro movimiento (cadena) | confirmed | +| confirmed | En espera | Confirmado, sin disponibilidad | assigned | +| partially_available | Parcial | Parcialmente reservado | assigned | +| assigned | Disponible | Stock reservado | done | +| done | Completado | Movimiento ejecutado | - | +| cancel | Cancelado | Movimiento cancelado | - | + +--- + +## 2. Estados del Albaran (stock.picking.state) + +| Estado | Nombre UI | Descripcion | Siguiente | +|--------|-----------|-------------|-----------| +| draft | Borrador | Estado inicial | confirmed | +| waiting | En espera | Esperando otra operacion | confirmed/assigned | +| confirmed | En espera | Esperando disponibilidad | assigned | +| assigned | Listo | Stock reservado, listo para procesar | done | +| done | Completado | Albaran validado | - | +| cancel | Cancelado | Albaran cancelado | - | + +--- + +## 3. Diagrama de Transiciones - stock.picking + +``` + ┌──────────────────┐ + │ DRAFT │ + │ (Borrador) │ + └────────┬─────────┘ + │ + action_confirm() + │ + ┌──────────────┴──────────────┐ + │ │ + ▼ ▼ + ┌──────────────────┐ ┌──────────────────┐ + │ CONFIRMED │ │ WAITING │ + │ (En espera) │ │ (Otra operacion) │ + └────────┬─────────┘ └────────┬─────────┘ + │ │ + │ action_assign() │ (dependencia resuelta) + │ │ + └──────────────┬────────────┘ + │ + ▼ + ┌──────────────────┐ + │ ASSIGNED │ + │ (Listo) │ + └────────┬─────────┘ + │ + button_validate() + _action_done() + │ + ▼ + ┌──────────────────┐ + │ DONE │ + │ (Completado) │ + └──────────────────┘ + +Estado paralelo (desde cualquier estado excepto done): + ┌──────────────────┐ + │ CANCEL │ + │ (Cancelado) │ + └──────────────────┘ +``` + +--- + +## 4. Diagrama de Transiciones - stock.move + +``` + ┌──────────────────┐ + │ DRAFT │ + │ (Nuevo) │ + └────────┬─────────┘ + │ + _action_confirm() + │ + ┌──────────────┴──────────────┐ + │ │ + ▼ ▼ + ┌──────────────────┐ ┌──────────────────┐ + │ CONFIRMED │ │ WAITING │ + │ (En espera) │ │ (Otro movimiento)│ + └────────┬─────────┘ └────────┬─────────┘ + │ │ + │ _action_assign() │ + │ │ + └──────────────┬────────────┘ + │ + ┌──────────────┼──────────────┐ + │ │ │ + ▼ ▼ ▼ + (sin stock) (stock parcial) (stock completo) + │ │ │ + │ ▼ │ + │ ┌─────────────────┐ │ + │ │ PARTIALLY_AVAIL │ │ + │ │ (Parcial) │ │ + │ └────────┬────────┘ │ + │ │ │ + └─────────────┼───────────────┘ + │ + ▼ + ┌──────────────────┐ + │ ASSIGNED │ + │ (Disponible) │ + └────────┬─────────┘ + │ + _action_done() + │ + ▼ + ┌──────────────────┐ + │ DONE │ + │ (Completado) │ + └──────────────────┘ +``` + +--- + +## 5. Flujo de Recepcion (Entrada de Proveedor) + +``` +1. CREACION + └─ stock.picking creado con picking_type_code='incoming' + └─ move_ids con state = 'draft' + +2. CONFIRMACION (action_confirm) + └─ Confirma todos los movimientos + └─ picking.state = 'confirmed' o 'assigned' + └─ move.state = 'confirmed' + +3. ASIGNACION (action_assign) + └─ Para recepciones: generalmente pasa directo a assigned + └─ No requiere reserva de stock (entra nuevo) + └─ picking.state = 'assigned' + └─ move.state = 'assigned' + +4. OPERACION + └─ Operario registra en move_line_ids + └─ Asigna quantity_done + └─ Opcionalmente asigna lot_id + +5. VALIDACION (button_validate) + └─ _action_done() ejecutado + └─ stock.quant creado/actualizado + └─ quantity incrementada en ubicacion destino + └─ picking.state = 'done' + └─ move.state = 'done' +``` + +--- + +## 6. Flujo de Envio (Salida a Cliente) + +``` +1. CREACION + └─ stock.picking creado con picking_type_code='outgoing' + └─ move_ids con state = 'draft' + +2. CONFIRMACION (action_confirm) + └─ Confirma movimientos + └─ picking.state = 'confirmed' + └─ Inicia proceso de reserva segun picking_type.reservation_method + +3. VERIFICACION DISPONIBILIDAD (action_assign) + └─ Busca stock en ubicacion origen + └─ Crea reservas en stock.quant (reserved_quantity) + + Si stock completo: + └─ move.state = 'assigned' + └─ picking.state = 'assigned' + + Si stock parcial: + └─ move.state = 'partially_available' + └─ picking.state = 'confirmed' (esperando) + + Si sin stock: + └─ move.state = 'confirmed' + └─ picking.state = 'waiting' o 'confirmed' + +4. OPERACION (Picking) + └─ Operario escanea/selecciona productos + └─ Registra quantity_done en move_line_ids + └─ Puede crear paquetes (action_put_in_pack) + +5. VALIDACION (button_validate) + └─ Si quantity_done < product_uom_qty: + └─ Opcion de crear backorder + └─ _action_done() ejecutado + └─ stock.quant.quantity reducida en origen + └─ Reservas liberadas + └─ picking.state = 'done' +``` + +--- + +## 7. Flujo de Transferencia Interna + +``` +1. CREACION + └─ stock.picking con picking_type_code='internal' + └─ location_id = origen interno + └─ location_dest_id = destino interno + +2. CONFIRMACION + └─ Similar a envio + +3. ASIGNACION + └─ Reserva stock en ubicacion origen + +4. OPERACION + └─ Mueve fisicamente productos + +5. VALIDACION + └─ Reduce quant en origen + └─ Incrementa quant en destino + └─ Sin cambio en stock total del almacen +``` + +--- + +## 8. Metodos de Transicion + +### stock.picking + +| Metodo | De Estado | A Estado | Descripcion | +|--------|-----------|----------|-------------| +| action_confirm() | draft | confirmed/waiting | Confirma albaran | +| action_assign() | confirmed | assigned | Reserva stock | +| action_cancel() | * | cancel | Cancela | +| button_validate() | assigned | done | Valida/completa | +| _action_done() | * | done | Marca completado | + +### stock.move + +| Metodo | De Estado | A Estado | Descripcion | +|--------|-----------|----------|-------------| +| _action_confirm() | draft | confirmed | Confirma movimiento | +| _action_assign() | confirmed | assigned | Reserva | +| _action_cancel() | * | cancel | Cancela | +| _action_done() | assigned | done | Ejecuta | + +--- + +## 9. Reglas de Negocio + +| ID | Regla | Validacion | Mensaje | +|----|-------|------------|---------| +| R1 | Producto requerido | product_id presente | Producto requerido | +| R2 | Cantidad positiva | product_uom_qty > 0 | Cantidad debe ser positiva | +| R3 | Ubicaciones validas | location_id y location_dest_id | Ubicaciones requeridas | +| R4 | Stock disponible | quant.available_quantity >= qty | Sin stock disponible | +| R5 | Lote requerido | Si tracking='lot'/'serial' | Lote/SN requerido | +| R6 | SN unico | Si tracking='serial', qty=1 | SN no acepta qty > 1 | + +--- + +## 10. Acciones Automaticas + +| Trigger | Accion | Condicion | +|---------|--------|-----------| +| action_confirm | Generar nombre secuencial | Si name = '/' | +| action_confirm | Reservar automatico | Si reservation_method = 'at_confirm' | +| button_validate | Crear backorder | Si create_backorder = 'always' | +| button_validate | Actualizar quants | Siempre | +| _action_done | Propagar a cadena | Si move_dest_ids | +| Scheduler | action_assign | Periodico | + +--- + +## 11. Permisos por Estado + +| Estado | Editable | Grupos | +|--------|----------|--------| +| draft | Si | stock.group_stock_user | +| confirmed | Parcial | stock.group_stock_user | +| waiting | No | - | +| assigned | Operacion | stock.group_stock_user | +| done | No | stock.group_stock_manager (forzar) | +| cancel | No | - | + +--- + +## 12. Flujo de Backorder + +``` +ALBARAN ORIGINAL (picking_1) + │ + │ button_validate() con cantidad parcial + │ create_backorder='always' o 'ask' + │ + ├─► ALBARAN COMPLETADO (picking_1) + │ state = 'done' + │ Cantidades procesadas + │ + └─► BACKORDER (picking_2) + state = 'confirmed' o 'assigned' + backorder_id = picking_1 + Cantidades pendientes +``` + +--- + +## 13. Flujo de Cadena de Movimientos + +``` +REGLA MTO (Make-to-Order) + │ + │ Crea movimiento con procure_method='make_to_order' + │ + ▼ +MOVIMIENTO 1 (move_orig) + state = 'waiting' (espera movimiento origen) + │ + │ move_orig_ids completado + │ + ▼ +MOVIMIENTO 2 (move_dest) + state = 'confirmed' → 'assigned' +``` + +--- + +## 14. Sistema de Reservas (Quants) + +``` +RESERVA DE STOCK + │ + ▼ +stock.quant + ├─ quantity = 100 (total) + ├─ reserved_quantity = 30 (reservado) + └─ available_quantity = 70 (computed) + +PROCESO: +1. action_assign() busca quants disponibles +2. Incrementa reserved_quantity +3. Crea stock.move.line vinculado +4. Al validar: reduce quantity, libera reserved_quantity +``` + +--- + +## 15. Estrategias de Extraccion + +| Estrategia | Descripcion | Uso | +|------------|-------------|-----| +| fifo | First In First Out | Productos perecederos | +| lifo | Last In First Out | Apilamiento | +| closest | Mas cercano | Optimizacion ruta | +| fefo | First Expiry First Out | Con fechas expiracion | + +--- + +**Referencias:** +- stock_picking.py: action_confirm, action_assign, button_validate +- stock_move.py: _action_confirm, _action_assign, _action_done +- stock_quant.py: _update_available_quantity diff --git a/shared/knowledge-base/reference/odoo/docs/90-transversal/CLASIFICACION-MODULOS.md b/shared/knowledge-base/reference/odoo/docs/90-transversal/CLASIFICACION-MODULOS.md new file mode 100644 index 000000000..d6b067764 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/90-transversal/CLASIFICACION-MODULOS.md @@ -0,0 +1,281 @@ +# Clasificacion de Modulos Odoo 18.0 + +**Fecha:** 2026-01-04 +**Version:** 1.0 +**Modulos Clasificados:** 10 (prioritarios) + +--- + +## 1. Clasificacion por Tipo + +### 1.1 Modulos de Infraestructura + +Modulos que proveen servicios fundamentales para otros modulos. + +| Modulo | Funcion | Caracteristica Clave | +|--------|---------|---------------------| +| base | Fundamento ORM | res.partner, res.users, ir.* | +| mail | Mensajeria | Chatter, actividades | +| analytic | Contabilidad analitica | Distribucion multi-cuenta | + +### 1.2 Modulos de Datos Maestros + +Modulos que definen entidades centrales del negocio. + +| Modulo | Entidad | Caracteristica Clave | +|--------|---------|---------------------| +| product | Productos | Variantes, atributos | +| hr | Empleados | Departamentos, versiones | + +### 1.3 Modulos de Proceso + +Modulos que implementan flujos de negocio con estados. + +| Modulo | Proceso | Estados | +|--------|---------|---------| +| account | Contabilidad | draft → posted → cancel | +| stock | Inventario | draft → confirmed → assigned → done | +| purchase | Compras | draft → sent → purchase → done | +| sale | Ventas | draft → sent → sale → done | +| project | Proyectos | in_progress → done/canceled | +| crm | Comercial | Lead → Opportunity → Won/Lost | + +--- + +## 2. Clasificacion por Complejidad + +### 2.1 Complejidad MUY ALTA + +Modulos con: +- Mas de 5,000 lineas de codigo +- Multiples modelos interconectados +- Workflows complejos con estados +- Integracion profunda con otros modulos + +| Modulo | Lineas | Modelos | Razon Complejidad | +|--------|--------|---------|-------------------| +| base | 20,000+ | 50+ | Fundamento de todo | +| account | 10,000+ | 15+ | Regulaciones contables | +| stock | 8,000+ | 12+ | Multi-almacen, valoracion | + +### 2.2 Complejidad ALTA + +Modulos con: +- 3,000-5,000 lineas +- Workflows definidos +- Logica de negocio significativa + +| Modulo | Lineas | Modelos | Razon Complejidad | +|--------|--------|---------|-------------------| +| product | 3,000+ | 8 | Variantes, atributos | +| project | 4,000+ | 6 | Subtareas, dependencias | +| crm | 3,500+ | 5 | Lead Scoring (ML) | + +### 2.3 Complejidad MEDIA-ALTA + +| Modulo | Lineas | Modelos | Razon Complejidad | +|--------|--------|---------|-------------------| +| purchase | 2,500+ | 4 | Flujo aprobacion | +| sale | 3,000+ | 5 | Firmas, pagos online | + +### 2.4 Complejidad MEDIA + +| Modulo | Lineas | Modelos | Razon Complejidad | +|--------|--------|---------|-------------------| +| hr | 2,000+ | 6 | Versionado empleados | +| analytic | 1,500+ | 5 | Distribucion JSON | + +--- + +## 3. Clasificacion por Patron de Implementacion + +### 3.1 Patron State Machine + +Modulos con campo `state` y transiciones definidas. + +``` +┌─────────┐ action_*() ┌─────────┐ action_*() ┌─────────┐ +│ draft │ ──────────────► │ confirm │ ──────────────► │ done │ +└─────────┘ └─────────┘ └─────────┘ + │ │ + └───────────────────────────┴──────────► cancel +``` + +**Modulos:** account, stock, purchase, sale, project + +### 3.2 Patron Stage-Based (Kanban) + +Modulos con campo `stage_id` para progreso visual. + +``` +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +│ New │ → │ Working │ → │ Review │ → │ Done │ +└──────────┘ └──────────┘ └──────────┘ └──────────┘ +``` + +**Modulos:** crm, project + +### 3.3 Patron Master Data + +Modulos sin workflow, solo CRUD de datos. + +**Modulos:** product, hr, analytic + +### 3.4 Patron Mixin + +Modulos que proveen funcionalidad reutilizable. + +| Modulo | Mixin | Funcion | +|--------|-------|---------| +| mail | mail.thread | Chatter en cualquier modelo | +| analytic | analytic.mixin | Distribucion analitica | +| base | portal.mixin | Acceso portal | + +--- + +## 4. Clasificacion por Dominio Funcional + +### 4.1 Finanzas y Contabilidad + +| Modulo | Funcion | Integracion | +|--------|---------|-------------| +| account | Libro mayor | purchase, sale, stock | +| analytic | Centros de costo | project, hr_timesheet | + +### 4.2 Cadena de Suministro + +| Modulo | Funcion | Integracion | +|--------|---------|-------------| +| stock | Inventario | purchase, sale | +| purchase | Compras | stock, account | + +### 4.3 Ventas y Marketing + +| Modulo | Funcion | Integracion | +|--------|---------|-------------| +| sale | Ventas | stock, account, crm | +| crm | Oportunidades | sale, contacts | + +### 4.4 Operaciones + +| Modulo | Funcion | Integracion | +|--------|---------|-------------| +| project | Gestion proyectos | analytic, hr_timesheet | +| hr | Empleados | project, expense | + +### 4.5 Datos Maestros + +| Modulo | Funcion | Usado Por | +|--------|---------|-----------| +| product | Catalogo | stock, sale, purchase | +| base | Contactos/Usuarios | TODOS | + +--- + +## 5. Clasificacion por Uso de Recursos + +### 5.1 Modulos CPU-Intensivos + +| Modulo | Operacion | Mitigacion | +|--------|-----------|------------| +| crm | Lead Scoring (PLS) | Batch processing | +| stock | Valoracion FIFO/AVCO | Queue jobs | +| account | Cierres contables | Async | + +### 5.2 Modulos IO-Intensivos + +| Modulo | Operacion | Mitigacion | +|--------|-----------|------------| +| stock | Movimientos masivos | Batch write | +| account | Reconciliacion | Indices PostgreSQL | + +### 5.3 Modulos Memoria-Intensivos + +| Modulo | Operacion | Mitigacion | +|--------|-----------|------------| +| product | Variantes multiples | Lazy loading | +| crm | Reportes pipeline | Pagination | + +--- + +## 6. Clasificacion por Riesgo de Cambios + +### 6.1 Riesgo CRITICO + +Cambios pueden romper multiples modulos. + +| Modulo | Razon | Precaucion | +|--------|-------|------------| +| base | Fundamento | Tests exhaustivos | +| account | Regulado | Cumplimiento legal | + +### 6.2 Riesgo ALTO + +Cambios afectan flujos de negocio importantes. + +| Modulo | Razon | Precaucion | +|--------|-------|------------| +| stock | Inventario fisico | Validacion stock | +| sale | Ingresos | Tests integracion | +| purchase | Costos | Aprobaciones | + +### 6.3 Riesgo MEDIO + +Cambios localizados pero visibles. + +| Modulo | Razon | Precaucion | +|--------|-------|------------| +| project | Productividad | UAT | +| crm | Pipeline ventas | Validacion usuarios | +| hr | Datos sensibles | Seguridad | + +### 6.4 Riesgo BAJO + +Modulos auxiliares o de soporte. + +| Modulo | Razon | +|--------|-------| +| analytic | Sin impacto directo en operaciones | + +--- + +## 7. Matriz de Clasificacion Consolidada + +| Modulo | Tipo | Complejidad | Patron | Dominio | Riesgo | +|--------|------|-------------|--------|---------|--------| +| base | Infra | MUY ALTA | N/A | Datos Maestros | CRITICO | +| product | Master | ALTA | Master Data | Datos Maestros | MEDIO | +| account | Proceso | MUY ALTA | State Machine | Finanzas | CRITICO | +| stock | Proceso | MUY ALTA | State Machine | Supply Chain | ALTO | +| purchase | Proceso | MEDIA-ALTA | State Machine | Supply Chain | ALTO | +| sale | Proceso | MEDIA-ALTA | State Machine | Ventas | ALTO | +| hr | Master | MEDIA | Master Data | Operaciones | MEDIO | +| crm | Proceso | ALTA | Stage-Based | Ventas | MEDIO | +| analytic | Infra | MEDIA | Mixin | Finanzas | BAJO | +| project | Proceso | ALTA | State+Stage | Operaciones | MEDIO | + +--- + +## 8. Recomendaciones por Clasificacion + +### Para Personalizacion +1. **Preferir herencia** sobre modificacion directa +2. **Modulos auxiliares** tienen menor riesgo +3. **Tests obligatorios** para modulos de proceso + +### Para Integracion +1. **Usar APIs oficiales** de modulos core +2. **Respetar estados** en workflows +3. **Validar dependencias** antes de desinstalar + +### Para Rendimiento +1. **Batch processing** para operaciones masivas +2. **Indices** para campos de busqueda frecuente +3. **Lazy loading** para modelos con muchos campos + +--- + +**Referencias:** +- Analisis de codigo: `odoo-18.0/addons/` +- Documentacion modelos: `03-modelado-datos/` +- Documentacion flujos: `04-logica-negocio/` diff --git a/shared/knowledge-base/reference/odoo/docs/90-transversal/INVENTARIO-MODULOS-ODOO.md b/shared/knowledge-base/reference/odoo/docs/90-transversal/INVENTARIO-MODULOS-ODOO.md new file mode 100644 index 000000000..6417f194c --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/90-transversal/INVENTARIO-MODULOS-ODOO.md @@ -0,0 +1,299 @@ +# Inventario de Modulos Odoo 18.0 + +**Fecha:** 2026-01-04 +**Version:** 1.0 +**Cobertura:** 10 modulos prioritarios documentados + +--- + +## 1. Resumen Ejecutivo + +| Metrica | Valor | +|---------|-------| +| Total modulos Odoo 18.0 | ~600 | +| Modulos core documentados | 2 | +| Modulos business documentados | 8 | +| Documentos MOD-*.md generados | 10 | +| Documentos MODELO-*.md generados | 10 | +| Documentos FLUJO-*.md generados | 7 | +| Documentos transversales | 3 | +| Total documentos | 30 | + +--- + +## 2. Modulos Core Documentados + +### 2.1 BASE + +| Atributo | Valor | +|----------|-------| +| Nombre Tecnico | base | +| Categoria | Hidden | +| Es Aplicacion | No (fundamento) | +| Archivo Principal | base_setup.py | +| Modelos Principales | res.users, res.partner, res.company, ir.model | +| Lineas Codigo | ~20,000+ | +| Complejidad | MUY ALTA | + +**Documentos:** +- `01-modulos-core/MOD-base.md` +- `03-modelado-datos/MODELO-base.md` +- `04-logica-negocio/FLUJO-base.md` + +--- + +### 2.2 PRODUCT + +| Atributo | Valor | +|----------|-------| +| Nombre Tecnico | product | +| Categoria | Sales/Sales | +| Es Aplicacion | No (datos maestros) | +| Archivo Principal | product_template.py | +| Modelos Principales | product.product, product.template, product.category | +| Lineas Codigo | 3,000+ | +| Complejidad | ALTA | + +**Documentos:** +- `01-modulos-core/MOD-product.md` +- `03-modelado-datos/MODELO-product.md` +- (Sin FLUJO - no tiene workflow) + +--- + +## 3. Modulos Business Documentados + +### 3.1 ACCOUNT (Contabilidad) + +| Atributo | Valor | +|----------|-------| +| Nombre Tecnico | account | +| Categoria | Accounting/Accounting | +| Es Aplicacion | Si | +| Modelo Principal | account.move | +| Estados | draft, posted, cancel | +| Lineas Codigo | 10,000+ | +| Complejidad | MUY ALTA | + +**Documentos:** +- `02-modulos-business/MOD-account.md` +- `03-modelado-datos/MODELO-account.md` +- `04-logica-negocio/FLUJO-account.md` + +--- + +### 3.2 STOCK (Inventario) + +| Atributo | Valor | +|----------|-------| +| Nombre Tecnico | stock | +| Categoria | Inventory/Inventory | +| Es Aplicacion | Si | +| Modelos Principales | stock.move (7 estados), stock.picking (6 estados) | +| Lineas Codigo | 8,000+ | +| Complejidad | MUY ALTA | + +**Documentos:** +- `02-modulos-business/MOD-stock.md` +- `03-modelado-datos/MODELO-stock.md` +- `04-logica-negocio/FLUJO-stock.md` + +--- + +### 3.3 PURCHASE (Compras) + +| Atributo | Valor | +|----------|-------| +| Nombre Tecnico | purchase | +| Categoria | Inventory/Purchase | +| Es Aplicacion | Si | +| Modelo Principal | purchase.order | +| Estados | draft, sent, to approve, purchase, cancel | +| Lineas Codigo | 2,500+ | +| Complejidad | MEDIA-ALTA | + +**Documentos:** +- `02-modulos-business/MOD-purchase.md` +- `03-modelado-datos/MODELO-purchase.md` +- `04-logica-negocio/FLUJO-purchase.md` + +--- + +### 3.4 SALE (Ventas) + +| Atributo | Valor | +|----------|-------| +| Nombre Tecnico | sale | +| Categoria | Sales/Sales | +| Es Aplicacion | Si | +| Modelo Principal | sale.order | +| Estados | draft, sent, sale, cancel | +| Lineas Codigo | 3,000+ | +| Complejidad | MEDIA-ALTA | + +**Documentos:** +- `02-modulos-business/MOD-sale.md` +- `03-modelado-datos/MODELO-sale.md` +- `04-logica-negocio/FLUJO-sale.md` + +--- + +### 3.5 HR (Recursos Humanos) + +| Atributo | Valor | +|----------|-------| +| Nombre Tecnico | hr | +| Categoria | Human Resources | +| Es Aplicacion | Si | +| Modelo Principal | hr.employee | +| Campos Empleado | 100+ | +| Lineas Codigo | 2,000+ | +| Complejidad | MEDIA | + +**Documentos:** +- `02-modulos-business/MOD-hr.md` +- `03-modelado-datos/MODELO-hr.md` +- (Sin FLUJO - modelo de configuracion) + +--- + +### 3.6 CRM (Oportunidades) + +| Atributo | Valor | +|----------|-------| +| Nombre Tecnico | crm | +| Categoria | Sales/CRM | +| Es Aplicacion | Si | +| Modelo Principal | crm.lead | +| Campos Lead | 80+ | +| Caracteristica Unica | Predictive Lead Scoring (PLS) | +| Lineas Codigo | 3,500+ | +| Complejidad | ALTA | + +**Documentos:** +- `02-modulos-business/MOD-crm.md` +- `03-modelado-datos/MODELO-crm.md` +- `04-logica-negocio/FLUJO-crm.md` + +--- + +### 3.7 ANALYTIC (Contabilidad Analitica) + +| Atributo | Valor | +|----------|-------| +| Nombre Tecnico | analytic | +| Categoria | Accounting/Accounting | +| Es Aplicacion | No (auxiliar) | +| Modelos Principales | account.analytic.account, account.analytic.line | +| Caracteristica Unica | Distribucion JSON multi-cuenta | +| Lineas Codigo | 1,500+ | +| Complejidad | MEDIA | + +**Documentos:** +- `02-modulos-business/MOD-analytic.md` +- `03-modelado-datos/MODELO-analytic.md` +- (Sin FLUJO - sin workflow) + +--- + +### 3.8 PROJECT (Proyectos) + +| Atributo | Valor | +|----------|-------| +| Nombre Tecnico | project | +| Categoria | Services/Project | +| Es Aplicacion | Si | +| Modelo Principal | project.task | +| Estados Tarea | 6 (4 open, 2 closed) | +| Caracteristicas | Subtareas, dependencias, recurrencia, milestones | +| Lineas Codigo | 4,000+ | +| Complejidad | ALTA | + +**Documentos:** +- `02-modulos-business/MOD-project.md` +- `03-modelado-datos/MODELO-project.md` +- `04-logica-negocio/FLUJO-project.md` + +--- + +## 4. Tabla Comparativa de Modulos + +| Modulo | Lineas | Modelos | Estados | Complejidad | +|--------|--------|---------|---------|-------------| +| base | 20,000+ | 50+ | N/A | MUY ALTA | +| product | 3,000+ | 8 | N/A | ALTA | +| account | 10,000+ | 15+ | 3 | MUY ALTA | +| stock | 8,000+ | 12+ | 7/6 | MUY ALTA | +| purchase | 2,500+ | 4 | 5 | MEDIA-ALTA | +| sale | 3,000+ | 5 | 4 | MEDIA-ALTA | +| hr | 2,000+ | 6 | N/A | MEDIA | +| crm | 3,500+ | 5 | N/A | ALTA | +| analytic | 1,500+ | 5 | N/A | MEDIA | +| project | 4,000+ | 6 | 6 | ALTA | + +--- + +## 5. Modulos Pendientes de Documentar + +### Prioridad Alta +| Modulo | Razon | +|--------|-------| +| mrp | Manufactura - critico para ERP industrial | +| website | Portal y e-commerce | +| pos | Punto de venta | + +### Prioridad Media +| Modulo | Razon | +|--------|-------| +| hr_expense | Gastos de empleados | +| hr_timesheet | Hojas de tiempo | +| fleet | Gestion de vehiculos | +| maintenance | Mantenimiento equipos | + +### Prioridad Baja +| Modulo | Razon | +|--------|-------| +| calendar | Calendario compartido | +| lunch | Comidas | +| survey | Encuestas | + +--- + +## 6. Estadisticas de Documentacion + +### Por Tipo de Documento +| Tipo | Cantidad | Descripcion | +|------|----------|-------------| +| MOD-*.md | 10 | Vision general del modulo | +| MODELO-*.md | 10 | Modelos de datos | +| FLUJO-*.md | 7 | Flujos de trabajo | +| Transversal | 3 | Documentacion cruzada | + +### Por Ubicacion +| Carpeta | Documentos | +|---------|------------| +| 01-modulos-core | 2 | +| 02-modulos-business | 8 | +| 03-modelado-datos | 10 | +| 04-logica-negocio | 7 | +| 90-transversal | 3 | + +--- + +## 7. Cobertura de Documentacion + +``` +Modulos Prioritarios: ████████████████████ 100% (10/10) +Documentos MOD: ████████████████████ 100% (10/10) +Documentos MODELO: ████████████████████ 100% (10/10) +Documentos FLUJO: ██████████████░░░░░░ 70% (7/10) +Transversales: ████████████████████ 100% (3/3) +``` + +Nota: 3 modulos no tienen FLUJO porque son de configuracion/datos maestros (product, hr, analytic). + +--- + +**Referencias:** +- Repositorio Odoo 18.0: `odoo-18.0/addons/` +- Documentacion generada: `docs/` diff --git a/shared/knowledge-base/reference/odoo/docs/90-transversal/MAPA-DEPENDENCIAS-MODULOS.md b/shared/knowledge-base/reference/odoo/docs/90-transversal/MAPA-DEPENDENCIAS-MODULOS.md new file mode 100644 index 000000000..757e887a9 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/90-transversal/MAPA-DEPENDENCIAS-MODULOS.md @@ -0,0 +1,200 @@ +# Mapa de Dependencias entre Modulos Odoo 18.0 + +**Fecha:** 2026-01-04 +**Version:** 1.1 (corregido) +**Modulos Documentados:** 10 (prioritarios) +**Fuente:** Manifests Odoo 18.0 + +--- + +## 1. Diagrama de Dependencias Principal + +``` + ┌─────────────────┐ + │ BASE │ + │ (Fundamento) │ + └────────┬────────┘ + │ + ┌──────────────────┼──────────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ + │ PRODUCT │ │ MAIL │ │ RESOURCE │ + │ (Productos) │ │ (Mensajeria) │ │ (Calendario) │ + └───────┬────────┘ └───────┬────────┘ └───────┬────────┘ + │ │ │ + ┌─────────┴─────────┐ │ │ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌────────────────┐ ┌────────────────┐ ┌────────────────────────────────┐ +│ ANALYTIC │ │ ACCOUNT │ │ HR │ +│ (Analitica) │ │ (Contabilidad) │ │ (Recursos Humanos) │ +└───────┬────────┘ └───────┬────────┘ └───────────────┬────────────────┘ + │ │ │ + │ ┌───────┴───────┐ │ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌────────────────────────┐ ┌────────────────┐ ┌────────────────┐ +│ PROJECT │ │ PURCHASE │ │ SALE │ +│ (Proyectos) │ │ (Compras) │ │ (Ventas) │ +└────────────────────────┘ └───────┬────────┘ └───────┬────────┘ + │ │ + └─────────┬─────────┘ + │ + ▼ + ┌────────────────┐ + │ STOCK │ + │ (Inventario) │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ CRM │ + │(Oportunidades) │ + └────────────────┘ +``` + +--- + +## 2. Matriz de Dependencias Directas (Odoo 18.0) + +| Modulo | Depende De (manifest) | Es Requerido Por | +|--------|----------------------|------------------| +| base | - | TODOS | +| product | base, mail, uom | stock, account (indirecto) | +| account | base_setup, onboarding, product, analytic, portal, digest | purchase, sale (indirecto) | +| stock | product, barcodes_gs1_nomenclature, digest | purchase_stock, sale_stock | +| purchase | account | purchase_stock, purchase_mrp | +| sale | sales_team, account_payment, utm | sale_stock, sale_mrp, sale_crm | +| hr | base_setup, digest, phone_validation, resource_mail, web | hr_timesheet, hr_expense, hr_attendance | +| crm | base_setup, sales_team, mail, calendar, resource, utm, web_tour, contacts, digest, phone_validation | sale_crm, website_crm | +| analytic | base, mail, uom | account, project, hr_timesheet | +| project | analytic, base_setup, mail, portal, rating, resource, web, web_tour, digest | hr_timesheet, project_account | + +**Nota:** Dependencias extraidas de `__manifest__.py` de Odoo 18.0. Las dependencias indirectas se resuelven transitivamente. + +--- + +## 3. Cadenas de Dependencia (Rutas Criticas) + +### 3.1 Cadena de Compras Completa +``` +base → product → account → purchase → purchase_stock → stock +``` + +### 3.2 Cadena de Ventas Completa +``` +base → product → account → sale → sale_stock → stock +``` + +### 3.3 Cadena de Proyectos con Hojas de Tiempo +``` +base → mail → analytic → project → hr_timesheet → hr +``` + +### 3.4 Cadena CRM a Ventas +``` +base → crm → sale_crm → sale → sale_stock → stock +``` + +--- + +## 4. Dependencias por Capa + +### Capa 0: Fundamento +| Modulo | Descripcion | +|--------|-------------| +| base | Modelos fundamentales (res.users, res.partner, res.company) | + +### Capa 1: Servicios Core +| Modulo | Descripcion | +|--------|-------------| +| mail | Sistema de mensajeria y actividades | +| resource | Calendario laboral | +| uom | Unidades de medida | +| phone | Validacion telefonica | + +### Capa 2: Datos Maestros +| Modulo | Descripcion | +|--------|-------------| +| product | Productos y variantes | +| hr | Empleados y departamentos | +| analytic | Contabilidad analitica | + +### Capa 3: Procesos de Negocio +| Modulo | Descripcion | +|--------|-------------| +| account | Contabilidad financiera | +| stock | Gestion de inventarios | +| project | Gestion de proyectos | +| crm | Gestion comercial | + +### Capa 4: Operaciones +| Modulo | Descripcion | +|--------|-------------| +| purchase | Ciclo de compras | +| sale | Ciclo de ventas | + +--- + +## 5. Impacto de Cambios + +### Alto Impacto (cambios afectan muchos modulos) +- **base**: Cambios aqui afectan TODOS los modulos +- **product**: Afecta stock, sale, purchase, account, mrp +- **account**: Afecta purchase, sale, payment, expenses + +### Medio Impacto +- **stock**: Afecta purchase_stock, sale_stock, mrp +- **mail**: Afecta comunicacion en todos los modulos + +### Bajo Impacto (modulos hoja) +- **crm**: Relativamente aislado +- **project**: Dependencias limitadas + +--- + +## 6. Orden de Instalacion Recomendado + +Para una implementacion limpia, instalar en este orden: + +``` +1. base (automatico) +2. mail, resource, uom +3. product +4. analytic +5. account +6. hr +7. stock +8. purchase, sale +9. project +10. crm +``` + +--- + +## 7. Conflictos Conocidos + +| Modulo A | Modulo B | Tipo Conflicto | +|----------|----------|----------------| +| stock | account | Valoracion inventario requiere ambos | +| sale | purchase | Intercompany puede crear loops | +| hr_timesheet | project | Campos duplicados si se instalan separado | + +--- + +## 8. Modulos Puente (Bridge Modules) + +| Modulo Puente | Conecta | Funcionalidad | +|---------------|---------|---------------| +| sale_stock | sale + stock | Entregas desde ventas | +| purchase_stock | purchase + stock | Recepciones desde compras | +| sale_crm | sale + crm | Conversion oportunidad a cotizacion | +| hr_timesheet | hr + project | Registro horas en tareas | +| project_account | project + account | Facturacion proyectos | + +--- + +**Referencias:** +- Manifests: `addons/*/\__manifest__.py` +- Documentacion modulos: `02-modulos-business/`, `03-modelado-datos/` diff --git a/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-0-ANALISIS-INICIAL.md b/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-0-ANALISIS-INICIAL.md new file mode 100644 index 000000000..69fcb222b --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-0-ANALISIS-INICIAL.md @@ -0,0 +1,409 @@ +# FASE 0: ANALISIS INICIAL DEL REPOSITORIO ODOO 18.0 + +**Fecha:** 2026-01-04 +**Version Odoo:** 18.0 +**Estado:** COMPLETADO + +--- + +## 1. INVENTARIO DE MODULOS + +### Conteo Total +- **Total de modulos en addons/**: 600 modulos +- **Modulos prioritarios identificados**: 9 modulos + +### Listado de Modulos Prioritarios +1. base +2. product +3. sale +4. purchase +5. stock +6. account +7. hr +8. crm +9. project + +--- + +## 2. ANALISIS DE MODULOS PRIORITARIOS + +### Tabla de Metadatos Completa + +| Modulo | Nombre | Version | Categoria | Modelos | Archivos | Application | Dependencias | +|--------|--------|---------|-----------|---------|----------|-------------|--------------| +| **base** | Base | 1.3 | Hidden | 49 | 693 | No | (ninguna) | +| **product** | Products & Pricelists | 1.2 | Sales/Sales | 26 | 226 | No | base, mail, uom | +| **sale** | Sales | 1.2 | Sales/Sales | 19 | 253 | No | sales_team, account_payment, utm | +| **purchase** | Purchase | 1.2 | Supply Chain/Purchase | 13 | 155 | Yes | account | +| **stock** | Inventory | 1.1 | Supply Chain/Inventory | 26 | 309 | Yes | product, barcodes_gs1_nomenclature, digest | +| **account** | Invoicing | 1.4 | Accounting/Accounting | 55 | 471 | Yes | base_setup, onboarding, product, analytic, portal, digest | +| **hr** | Employees | 1.1 | Human Resources/Employees | 26 | 262 | Yes | base_setup, digest, phone_validation, resource_mail, web | +| **crm** | CRM | 1.9 | Sales/CRM | 16 | 205 | Yes | base_setup, sales_team, mail, calendar, resource, utm, web_tour, contacts, digest, phone_validation | +| **project** | Project | 1.3 | Services/Project | 20 | 398 | Yes | analytic, base_setup, mail, portal, rating, resource, web, web_tour, digest | + +### Modelos Principales por Modulo + +#### base (49 modelos) +- ir_rule.py - Control de acceso basado en reglas +- ir_module.py - Gestion de modulos +- ir_cron.py - Tareas programadas +- ir_model.py - Metamodelo ORM +- res_groups.py - Grupos de seguridad +- res_bank.py - Informacion bancaria +- ir_exports.py - Exportacion de datos +- decimal_precision.py - Precision decimal +- report_paperformat.py - Formato de papeles de reporte + +#### product (26 modelos) +- product_template.py - Plantilla de producto +- product_product.py - Variantes de producto +- product_attribute.py - Atributos de producto +- product_pricelist.py - Listas de precios +- product_supplierinfo.py - Informacion de proveedores +- product_tag.py - Etiquetas de productos +- product_combo_item.py - Items combinados +- product_uom.py - Unidades de medida + +#### sale (19 modelos) +- sale_order.py - Orden de venta +- sale_order_line.py - Lineas de orden de venta +- account_move.py - Integracion contable +- account_move_line.py - Lineas de movimientos +- crm_team.py - Equipos de venta +- res_partner.py - Clientes/proveedores + +#### purchase (13 modelos) +- purchase_order.py - Orden de compra +- purchase_order_line.py - Lineas de orden +- purchase_bill_line_match.py - Validacion de facturas +- account_move.py - Integracion contable + +#### stock (26 modelos) +- stock_move.py - Movimientos de inventario +- stock_picking.py - Recepcion/envios +- stock_location.py - Ubicaciones de almacen +- stock_quant.py - Cantidades en stock +- stock_rule.py - Reglas de reabastecimiento +- stock_scrap.py - Residuos +- stock_orderpoint.py - Puntos de pedido +- stock_warehouse.py - Almacenes + +#### account (55 modelos) +- account_move.py - Movimientos contables (el mas critico) +- account_journal.py - Diarios contables +- account_account.py - Cuentas +- account_tax.py - Impuestos +- account_bank_statement.py - Extractos bancarios +- account_reconcile_model.py - Modelos de conciliacion +- account_full_reconcile.py - Conciliacion completa +- account_analytic_plan.py - Planes analiticos +- account_payment.py - Pagos + +#### hr (26 modelos) +- hr_employee.py - Empleados +- hr_department.py - Departamentos +- hr_contract.py - Contratos +- hr_job.py - Puestos de trabajo +- hr_employee_category.py - Categorias de empleados +- hr_work_location.py - Ubicaciones de trabajo +- hr_departure_reason.py - Motivos de salida + +#### crm (16 modelos) +- crm_lead.py - Leads/Oportunidades +- crm_team.py - Equipos de venta +- crm_stage.py - Etapas de venta +- crm_team_member.py - Miembros de equipos +- crm_lost_reason.py - Motivos de perdida +- crm_recurring_plan.py - Planes recurrentes + +#### project (20 modelos) +- project_project.py - Proyectos +- project_task.py - Tareas +- project_milestone.py - Hitos +- project_update.py - Actualizaciones de proyecto +- project_project_stage.py - Estados del proyecto +- project_role.py - Roles en proyectos +- project_tags.py - Etiquetas de tareas + +--- + +## 3. MAPA DE DEPENDENCIAS + +### Grafo de Dependencias Simplificado + +``` + BASE (kernel) + / | | \ \ + / | | \ \ + PRODUCT--+ CONTACTS WEB RESOURCE + | | \ | | | + | | MAIL | RESOURCE_MAIL + | | | | | + UOM | CALENDAR| | + | | | | + +-----+-----+-----+--+ + | + +---------+----------+----------+ + | | | | + ACCOUNT STOCK SALES_TEAM DIGEST + | | | | + | | ACCOUNT_PAYMENT + | | | + PURCHASE---+--+--------SALE + | | + +----+---+------+----+ + | | | + ANALYTIC PORTAL + | | + +---PROJECT + | + CRM (independent) + | + HR (independent) +``` + +### Tabla de Dependencias Directas + +| Modulo | Depende De | # Deps | Dependido Por | +|--------|-----------|--------|---------------| +| base | - | 0 | Todos (9) | +| product | base, mail, uom | 3 | sale, stock, account, purchase | +| sale | sales_team, account_payment, utm | 3 | purchase, project | +| purchase | account | 1 | stock | +| stock | product, barcodes_gs1_nomenclature, digest | 3 | sale, project | +| account | base_setup, onboarding, product, analytic, portal, digest | 6 | sale, purchase, project | +| hr | base_setup, digest, phone_validation, resource_mail, web | 5 | (independent) | +| crm | base_setup, sales_team, mail, calendar, resource, utm, web_tour, contacts, digest, phone_validation | 10 | (independent) | +| project | analytic, base_setup, mail, portal, rating, resource, web, web_tour, digest | 9 | (independent) | + +--- + +## 4. ESTIMACION DE COMPLEJIDAD + +### Matriz de Complejidad Detallada + +| Modulo | Modelos | Archivos | Complejidad | Explicacion | +|--------|---------|----------|-------------|-------------| +| **base** | 49 | 693 | **ALTA** | Kernel fundamental, 49 modelos para infraestructura, seguridad, modulos | +| **account** | 55 | 471 | **ALTA** | Sistema contable completo, 55 modelos, impuestos, reconciliacion | +| **product** | 26 | 226 | **MEDIA-ALTA** | Atributos, variantes, combos, precios, proveedores | +| **stock** | 26 | 309 | **MEDIA-ALTA** | Almacenes, reglas complejas, trazabilidad, ubicaciones anidadas | +| **project** | 20 | 398 | **MEDIA-ALTA** | Colaboracion, proyectos, tareas, hitos, compartir | +| **hr** | 26 | 262 | **MEDIA-ALTA** | Empleados, departamentos, contratos, informacion personal | +| **sale** | 19 | 253 | **MEDIA** | Ordenes, lineas, cotizaciones, integracion de pagos | +| **crm** | 16 | 205 | **MEDIA** | Leads, oportunidades, equipos, prediccion | +| **purchase** | 13 | 155 | **MEDIA-BAJA** | Ordenes de compra simplificadas, validacion de facturas | + +### Distribucion de Complejidad + +``` +ALTA (2 modulos, 104 modelos) +├── base: 49 modelos, 693 archivos (kernel) +└── account: 55 modelos, 471 archivos (contabilidad) + +MEDIA-ALTA (4 modulos, 98 modelos) +├── product: 26 modelos, 226 archivos +├── stock: 26 modelos, 309 archivos +├── project: 20 modelos, 398 archivos +└── hr: 26 modelos, 262 archivos + +MEDIA (2 modulos, 35 modelos) +├── sale: 19 modelos, 253 archivos +└── crm: 16 modelos, 205 archivos + +MEDIA-BAJA (1 modulo, 13 modelos) +└── purchase: 13 modelos, 155 archivos + +Total: 9 modulos, 250 modelos, 2,372 archivos +``` + +--- + +## 5. ORDEN SUGERIDO DE ANALISIS + +### Estrategia General: Bottom-Up (Dependencias Primero) + +#### Grupo 1: Fundamentos +1. **base** (ALTA complejidad, sin dependencias) + - Archivos a generar: MOD-base.md, MODELO-base.md + - Validacion: N/A (primer modulo) + - Esfuerzo estimado: 40-50 horas + +#### Grupo 2: Catalogo de Productos +2. **product** (MEDIA-ALTA complejidad, depende de base) + - Archivos a generar: MOD-product.md, MODELO-product.md + - Validacion: verificar refs a base + - Esfuerzo estimado: 25-30 horas + +#### Grupo 3: Sistema Contable +3. **account** (ALTA complejidad, depende de base, product) + - Archivos a generar: MOD-account.md, MODELO-account.md, FLUJO-contabilidad.md + - Validacion: verificar refs a base, product + - Esfuerzo estimado: 45-60 horas + +#### Grupo 4: Gestion de Inventario +4. **stock** (MEDIA-ALTA complejidad, depende de base, product) + - Archivos a generar: MOD-stock.md, MODELO-stock.md, FLUJO-inventario.md + - Validacion: verificar refs a base, product + - Esfuerzo estimado: 30-35 horas + +#### Grupo 5: Compras y Ventas +5. **purchase** (MEDIA-BAJA complejidad, depende de account) + - Esfuerzo estimado: 12-15 horas + +6. **sale** (MEDIA complejidad, depende de account, product) + - Esfuerzo estimado: 20-25 horas + +#### Grupo 6: Recursos Humanos y CRM +7. **hr** (MEDIA-ALTA complejidad, independiente) + - Esfuerzo estimado: 25-30 horas + +8. **crm** (MEDIA complejidad, independiente) + - Esfuerzo estimado: 18-22 horas + +#### Grupo 7: Gestion de Proyectos +9. **project** (MEDIA-ALTA complejidad, depende de analytic) + - Esfuerzo estimado: 30-35 horas + +### Matriz de Precedencia + +``` +base (1) + | + +--- product (2) ----+ + | | + +--- account (3) ----+---- sale (6) ----+ + | | | + +--- hr (7) +---- purchase (5) -+ + | | + +--- crm (8) +---- stock (4) ----+ + | | + +--- project (9) ---+ +``` + +--- + +## 6. OBSERVACIONES RELEVANTES + +### Arquitectura General + +1. **Modularidad**: Odoo 18.0 mantiene una arquitectura modular clara donde: + - **base** es el cimiento (49 modelos, 693 archivos) + - **product** es fundamental para muchos modulos (26 modelos) + - **account** es critico para finanzas (55 modelos, mayor complejidad) + - Los demas modulos se construyen sobre estas bases + +2. **Dependencias Estrategicas**: + - Pocos modulos tienen dependencias circulares + - Las dependencias forman un grafo aciclico dirigido (DAG) + - Los modulos de aplicacion (application: true) son: purchase, stock, account, hr, crm, project + +3. **Patron de Extensibilidad**: + - Los modulos usan mixins (ej: product_catalog_mixin en product, stock, account) + - Sistema de herencia ORM permite extender modelos + - Seguridad granular con ir.model.access y ir_rules + +### Caracteristicas Clave por Modulo + +| Modulo | Caracteristicas Principales | +|--------|---------------------------| +| **base** | Kernel de Odoo, gestion de usuarios, modulos, seguridad | +| **product** | Catalogo de productos, atributos, variantes, proveedores, precios, combos | +| **account** | Contabilidad general, diarios, movimientos, impuestos, conciliacion, bancos | +| **stock** | Inventario, ubicaciones, movimientos, picking, trazabilidad, reabastecimiento | +| **sale** | Ordenes de venta, lineas, cotizaciones, pagos, generacion de facturas | +| **purchase** | Ordenes de compra, solicitudes, acuerdos, validacion con facturas | +| **hr** | Empleados, departamentos, contratos, informacion personal, documentos | +| **crm** | Leads, oportunidades, equipos de venta, prediccion de ventas | +| **project** | Proyectos, tareas, hitos, recursos, actividades, compartir proyecto | + +### Puntos Criticos de Integracion + +1. **account_move** - Centro de datos contables + - Referenciado por: sale, purchase, stock, hr + - Integracion ineludible para cualquier transaccion + +2. **product.template / product.product** - Catalogo central + - Referenciado por: sale, purchase, stock, account, project + - Modelo fundamental para cotizaciones + +3. **res_partner** - Terceros (clientes/proveedores) + - Modificado por: sale, purchase, crm, account, project + - Central para relaciones comerciales + +4. **stock_move / stock_picking** - Movimientos fisicos + - Integracion con: sale, purchase, account, hr + - Critico para cadena de suministro + +--- + +## 7. ESTADISTICAS FINALES + +### Resumen Cuantitativo + +``` +REPOSITORIO TOTAL: +├─ Total de modulos: 600 +├─ Modulos prioritarios analizados: 9 +├─ Total de modelos (prioritarios): 250 +├─ Total de archivos (prioritarios): 2,372 +├─ Lineas de codigo estimadas (prioritarios): 95,000+ +└─ Esfuerzo total de analisis estimado: 270-340 horas + +DISTRIBUCION POR MODULO: +├─ base: 49 modelos (19.6%), 693 archivos (29.2%) +├─ account: 55 modelos (22%), 471 archivos (19.9%) +├─ project: 20 modelos (8%), 398 archivos (16.8%) +├─ stock: 26 modelos (10.4%), 309 archivos (13%) +├─ sale: 19 modelos (7.6%), 253 archivos (10.7%) +├─ hr: 26 modelos (10.4%), 262 archivos (11%) +├─ product: 26 modelos (10.4%), 226 archivos (9.5%) +├─ crm: 16 modelos (6.4%), 205 archivos (8.6%) +└─ purchase: 13 modelos (5.2%), 155 archivos (6.5%) + +COMPLEJIDAD: +├─ ALTA: 2 modulos (base, account) +├─ MEDIA-ALTA: 4 modulos (product, stock, project, hr) +├─ MEDIA: 2 modulos (sale, crm) +└─ MEDIA-BAJA: 1 modulo (purchase) +``` + +--- + +## 8. MATRIZ DE DECISION: PRIORIDAD DE ANALISIS + +| Modulo | Criticidad | Dependencia | Complejidad | Impacto | Prioridad | Fase | +|--------|-----------|------------|------------|--------|----------|------| +| base | 10 | 10 | 9 | 10 | 9.75 | 1 | +| account | 10 | 8 | 9 | 10 | 9.25 | 3 | +| product | 9 | 10 | 7 | 9 | 8.75 | 2 | +| stock | 9 | 8 | 7 | 9 | 8.25 | 4 | +| sale | 8 | 7 | 6 | 8 | 7.25 | 6 | +| project | 7 | 6 | 7 | 7 | 6.75 | 8 | +| hr | 7 | 6 | 7 | 6 | 6.5 | 7 | +| crm | 6 | 5 | 6 | 6 | 5.75 | 7 | +| purchase | 5 | 7 | 4 | 5 | 5.25 | 5 | + +--- + +## 9. PROXIMOS PASOS + +### Para FASE 1 (Planeacion Detallada): +- [ ] Definir estructura de documentacion por modulo +- [ ] Crear templates para MOD, MODELO, FLUJO +- [ ] Establecer puntos de validacion entre modulos +- [ ] Definir criterios de exito por modulo + +### Para FASE 2 (Analisis de base): +- [ ] Analizar estructura de base/models/ +- [ ] Documentar 49 modelos principales +- [ ] Mapear relaciones entre modelos base +- [ ] Identificar APIs publicas importantes + +--- + +**Generado:** 2026-01-04 +**Version:** 1.0 +**Estado:** COMPLETADO +**Siguiente Fase:** FASE 1 - Planeacion Detallada + diff --git a/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-1-PLAN-ANALISIS.md b/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-1-PLAN-ANALISIS.md new file mode 100644 index 000000000..571508dff --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-1-PLAN-ANALISIS.md @@ -0,0 +1,533 @@ +# FASE 1: PLAN DE ANALISIS DETALLADO DE ODOO 18.0 + +**Fecha:** 2026-01-04 +**Version:** 1.0 +**Estado:** COMPLETADO +**Basado en:** FASE-0-ANALISIS-INICIAL.md + +--- + +## 1. ORDEN DE EJECUCION + +### Estrategia: Bottom-Up por Dependencias + +El orden respeta el grafo de dependencias identificado en FASE 0. Los modulos se analizan +de forma que cada modulo solo se analice cuando sus dependencias ya esten documentadas. + +### Secuencia de Analisis + +| Orden | Modulo | Dependencias Requeridas | Complejidad | Archivos a Generar | +|-------|--------|------------------------|-------------|-------------------| +| 1 | **base** | Ninguna | ALTA | MOD, MODELO, FLUJO | +| 2 | **product** | base | MEDIA-ALTA | MOD, MODELO, FLUJO | +| 3 | **account** | base, product | ALTA | MOD, MODELO, FLUJO | +| 4 | **stock** | base, product | MEDIA-ALTA | MOD, MODELO, FLUJO | +| 5 | **purchase** | account | MEDIA-BAJA | MOD, MODELO, FLUJO | +| 6 | **sale** | account, product | MEDIA | MOD, MODELO, FLUJO | +| 7 | **hr** | base (independiente) | MEDIA-ALTA | MOD, MODELO, FLUJO | +| 8 | **crm** | base (independiente) | MEDIA | MOD, MODELO, FLUJO | +| 9 | **project** | base, analytic | MEDIA-ALTA | MOD, MODELO, FLUJO | + +### Diagrama de Precedencia + +``` +FASE A: Fundamentos +┌─────────────────────────────────────────────────────────────┐ +│ [1] base │ +│ └── Kernel fundamental, sin dependencias │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +FASE B: Catalogo Central +┌─────────────────────────────────────────────────────────────┐ +│ [2] product │ +│ └── Depende de: base │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +FASE C: Contabilidad y Logistica +┌─────────────────────────────────────────────────────────────┐ +│ [3] account ◄─────────────────────► [4] stock │ +│ └── Depende de: base, product └── Depende de: base, product │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +FASE D: Transacciones Comerciales +┌─────────────────────────────────────────────────────────────┐ +│ [5] purchase ◄────────────────────► [6] sale │ +│ └── Depende de: account └── Depende de: account, product │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +FASE E: Recursos y Proyectos (Independientes) +┌─────────────────────────────────────────────────────────────┐ +│ [7] hr [8] crm [9] project │ +│ (independiente) (independiente) (depende de analytic) │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. ESTRUCTURA DE DOCUMENTACION POR MODULO + +### 2.1 Archivos a Generar por Modulo + +Para cada modulo se generaran 3 tipos de documentos: + +| Tipo | Ubicacion | Proposito | +|------|-----------|-----------| +| MOD-[modulo].md | `02-modulos-business/` | Vision general del modulo | +| MODELO-[modulo].md | `03-modelado-datos/` | Tablas, campos y relaciones | +| FLUJO-[modulo].md | `04-logica-negocio/` | Estados, transiciones, reglas | + +### 2.2 Archivos Transversales + +| Archivo | Ubicacion | Proposito | +|---------|-----------|-----------| +| MAPA-DEPENDENCIAS-MODULOS.md | `90-transversal/` | Grafo completo de dependencias | +| INVENTARIO-MODULOS-ODOO.md | `90-transversal/` | Lista completa de 600 modulos | +| CLASIFICACION-MODULOS.md | `90-transversal/` | Categorizacion por dominio | + +### 2.3 Archivos de Integracion + +| Archivo | Ubicacion | Proposito | +|---------|-----------|-----------| +| API-XMLRPC.md | `05-apis-integraciones/` | API XML-RPC de Odoo | +| API-REST.md | `05-apis-integraciones/` | API REST (si existe) | + +--- + +## 3. TEMPLATES DE DOCUMENTACION + +### Template: MOD-[modulo].md + +```markdown +# Modulo: [Nombre Completo] + +**Odoo Module:** [nombre_tecnico] +**Version:** [version] +**Categoria:** [categoria] +**Es Aplicacion:** [Si/No] + +--- + +## 1. Descripcion General + +[Descripcion del proposito y funcionalidad principal del modulo] + +## 2. Dependencias + +### 2.1 Dependencias Explicitas (manifest) + +| Modulo | Tipo | Descripcion | +|--------|------|-------------| +| base | Requerido | [descripcion] | + +### 2.2 Dependencias Implicitas (uso de modelos) + +| Modelo Externo | Campo | Modulo Origen | +|---------------|-------|---------------| +| res.partner | partner_id | base | + +## 3. Modelos Principales + +| Modelo | Descripcion | Archivo Fuente | +|--------|-------------|----------------| +| sale.order | Orden de venta | models/sale_order.py | + +## 4. Integraciones con Otros Modulos + +[Como se integra con otros modulos del ecosistema] + +## 5. Notas de Implementacion + +[Observaciones relevantes para implementadores] + +--- + +**Referencias:** +- Archivo fuente: `addons/[modulo]/` +- Manifest: `addons/[modulo]/__manifest__.py` +``` + +### Template: MODELO-[modulo].md + +```markdown +# Modelo de Datos: [Nombre Modulo] + +**Modulo:** [nombre_tecnico] +**Total Modelos:** [numero] +**Total Campos Documentados:** [numero] + +--- + +## 1. Indice de Modelos + +| Modelo | Descripcion | Campos | Archivo | +|--------|-------------|--------|---------| +| sale.order | Orden de venta | 45 | sale_order.py | + +--- + +## 2. Detalle por Modelo + +### 2.1 [nombre.modelo] + +**Archivo:** `models/[archivo].py` +**Descripcion:** [descripcion] +**Hereda de:** [modelo padre si aplica] + +#### Campos + +| Campo | Tipo | FK/Rel | Req | Default | Descripcion | +|-------|------|--------|-----|---------|-------------| +| id | Integer | PK | Si | Auto | Identificador | +| name | Char(256) | - | Si | - | Nombre | +| partner_id | Many2one | res.partner | Si | - | Cliente | + +#### Campos Computed + +| Campo | Depende de | Metodo | Store | +|-------|------------|--------|-------| +| amount_total | line_ids.price_total | _compute_amounts | Si | + +#### Constraints + +| Nombre | Tipo | Campos | Mensaje | +|--------|------|--------|---------| +| check_date | CHECK | date_order | Fecha debe ser valida | + +#### Relaciones + +``` +[modelo] ──Many2one──► res.partner (partner_id) +[modelo] ◄──One2many── sale.order.line (order_id) +``` + +--- + +## 3. Diagrama Entidad-Relacion + +``` +┌─────────────────┐ ┌─────────────────┐ +│ sale.order │──────►│ res.partner │ +│ │ │ │ +│ - name │ │ - name │ +│ - partner_id │ │ - email │ +│ - amount_total │ │ │ +└─────────────────┘ └─────────────────┘ + │ + │ One2many + ▼ +┌─────────────────┐ +│ sale.order.line │ +│ │ +│ - product_id │ +│ - quantity │ +│ - price_unit │ +└─────────────────┘ +``` + +--- + +**Referencias:** +- Carpeta models: `addons/[modulo]/models/` +``` + +### Template: FLUJO-[modulo].md + +```markdown +# Flujo de Negocio: [Nombre Modulo] + +**Modulo:** [nombre_tecnico] +**Modelo Principal:** [modelo con estados] + +--- + +## 1. Estados del Documento + +| Estado | Nombre UI | Descripcion | Siguiente | +|--------|-----------|-------------|-----------| +| draft | Borrador | Estado inicial | confirm | +| confirmed | Confirmado | Pedido validado | done, cancel | +| done | Completado | Pedido finalizado | - | +| cancel | Cancelado | Pedido anulado | draft | + +--- + +## 2. Diagrama de Transiciones + +``` + ┌──────────────┐ + │ DRAFT │ + │ (Borrador) │ + └──────┬───────┘ + │ + action_confirm() + │ + ▼ + ┌──────────────┐ + │ CONFIRMED │ + │ (Confirmado) │ + └──────┬───────┘ + │ + ┌────────────┼────────────┐ + │ │ + action_done() action_cancel() + │ │ + ▼ ▼ + ┌──────────────┐ ┌──────────────┐ + │ DONE │ │ CANCEL │ + │ (Completado) │ │ (Cancelado) │ + └──────────────┘ └──────────────┘ +``` + +--- + +## 3. Metodos de Transicion + +| Metodo | De Estado | A Estado | Validaciones | +|--------|-----------|----------|--------------| +| action_confirm | draft | confirmed | Requiere lineas | +| action_done | confirmed | done | Requiere factura | +| action_cancel | * | cancel | - | +| action_draft | cancel | draft | Solo admin | + +--- + +## 4. Reglas de Negocio + +| ID | Regla | Validacion | Mensaje Error | +|----|-------|------------|---------------| +| R1 | Requiere cliente | partner_id required | Debe seleccionar cliente | +| R2 | Minimo una linea | len(line_ids) > 0 | Debe tener productos | + +--- + +## 5. Acciones Automaticas + +| Trigger | Accion | Condicion | +|---------|--------|-----------| +| on_confirm | Crear picking | Si hay productos almacenables | +| on_done | Crear factura | Si auto_invoice = True | + +--- + +## 6. Permisos por Estado + +| Estado | Editable | Grupos con Acceso | +|--------|----------|-------------------| +| draft | Si | sales_team.group_sale_salesman | +| confirmed | Parcial | sales_team.group_sale_manager | +| done | No | - | + +--- + +**Referencias:** +- Archivo principal: `models/[modelo].py` +- Acciones server: `data/[modulo]_data.xml` +``` + +--- + +## 4. PUNTOS DE VALIDACION + +### Validacion Entre Modulos + +| Punto | Despues de | Validar | Accion si Falla | +|-------|------------|---------|-----------------| +| V1 | base | Estructura correcta | Corregir template | +| V2 | product | Refs a base OK | Actualizar base | +| V3 | account | Refs a base, product OK | Actualizar anteriores | +| V4 | stock | Refs a base, product OK | Actualizar anteriores | +| V5 | purchase | Refs a account OK | Actualizar account | +| V6 | sale | Refs a account, product OK | Actualizar anteriores | +| V7 | hr | Independiente | N/A | +| V8 | crm | Independiente | N/A | +| V9 | project | Refs a analytic OK | Documentar analytic si necesario | + +### Checklist de Validacion por Modulo + +```markdown +## Checklist Validacion [MODULO] + +### Documentacion +- [ ] MOD-[modulo].md existe y completo +- [ ] MODELO-[modulo].md existe y completo +- [ ] FLUJO-[modulo].md existe y completo (si aplica estados) + +### Contenido MOD +- [ ] Descripcion general clara +- [ ] Dependencias explicitas listadas +- [ ] Dependencias implicitas identificadas +- [ ] Modelos principales tabulados +- [ ] Referencias a archivos fuente + +### Contenido MODELO +- [ ] Todos los modelos documentados +- [ ] Todos los campos con tipo correcto +- [ ] Relaciones FK/Many2one identificadas +- [ ] Constraints documentadas +- [ ] Campos computed con dependencias + +### Contenido FLUJO +- [ ] Estados documentados +- [ ] Transiciones con metodos +- [ ] Reglas de negocio listadas +- [ ] Permisos por estado (si aplica) + +### Coherencia +- [ ] Refs a otros modulos existen en docs +- [ ] Relaciones bidireccionales documentadas +- [ ] No hay referencias rotas +``` + +--- + +## 5. CRONOGRAMA DE EJECUCION + +### Estimacion de Esfuerzo + +| Modulo | Modelos | Complejidad | Esfuerzo Estimado | +|--------|---------|-------------|-------------------| +| base | 49 | ALTA | 40-50 tareas | +| product | 26 | MEDIA-ALTA | 25-30 tareas | +| account | 55 | ALTA | 45-60 tareas | +| stock | 26 | MEDIA-ALTA | 30-35 tareas | +| purchase | 13 | MEDIA-BAJA | 12-15 tareas | +| sale | 19 | MEDIA | 20-25 tareas | +| hr | 26 | MEDIA-ALTA | 25-30 tareas | +| crm | 16 | MEDIA | 18-22 tareas | +| project | 20 | MEDIA-ALTA | 30-35 tareas | +| **TOTAL** | **250** | - | **245-302 tareas** | + +### Fases de Ejecucion + +| Fase | Modulos | Documentos Generados | Validaciones | +|------|---------|---------------------|--------------| +| A | base | 3 docs | V1 | +| B | product | 3 docs | V2 | +| C | account, stock | 6 docs | V3, V4 | +| D | purchase, sale | 6 docs | V5, V6 | +| E | hr, crm, project | 9 docs | V7, V8, V9 | +| FINAL | transversales | 3-5 docs | Validacion integral | + +--- + +## 6. CRITERIOS DE EXITO POR MODULO + +### Criterios Cuantitativos + +| Metrica | Objetivo | +|---------|----------| +| % Modelos documentados | 100% de modelos principales | +| % Campos documentados | >= 90% de campos | +| % Estados documentados | 100% si tiene workflow | +| Referencias rotas | 0 | + +### Criterios Cualitativos + +- La documentacion es suficiente para entender el modulo sin ver codigo +- Las dependencias estan claramente identificadas +- Un desarrollador podria replicar la funcionalidad basandose en la documentacion +- Los flujos de negocio son comprensibles para no-tecnicos + +--- + +## 7. ENTREGABLES FASE 4 + +Al completar la FASE 4 (Ejecucion), se habran generado: + +### Por Modulo Prioritario (9 modulos) + +``` +02-modulos-business/ +├── MOD-base.md +├── MOD-product.md +├── MOD-account.md +├── MOD-stock.md +├── MOD-purchase.md +├── MOD-sale.md +├── MOD-hr.md +├── MOD-crm.md +└── MOD-project.md + +03-modelado-datos/ +├── MODELO-base.md +├── MODELO-product.md +├── MODELO-account.md +├── MODELO-stock.md +├── MODELO-purchase.md +├── MODELO-sale.md +├── MODELO-hr.md +├── MODELO-crm.md +└── MODELO-project.md + +04-logica-negocio/ +├── FLUJO-base.md +├── FLUJO-ventas.md +├── FLUJO-compras.md +├── FLUJO-inventario.md +├── FLUJO-contabilidad.md +├── FLUJO-recursos-humanos.md +├── FLUJO-crm.md +└── FLUJO-proyectos.md +``` + +### Documentos Transversales + +``` +90-transversal/ +├── MAPA-DEPENDENCIAS-MODULOS.md +├── INVENTARIO-MODULOS-ODOO.md +└── CLASIFICACION-MODULOS.md + +05-apis-integraciones/ +├── API-XMLRPC.md +└── API-REST.md (si existe) +``` + +### Documentos de Control + +``` +_analisis/ +├── FASE-0-ANALISIS-INICIAL.md (existente) +├── FASE-1-PLAN-ANALISIS.md (este documento) +├── FASE-2-VALIDACION-PLAN.md (siguiente) +├── FASE-3-PLAN-REFINADO-FINAL.md +├── MODULO-[nombre]-CRUDO.md (por modulo) +├── VALIDACION-DEPS-[modulo].md (por modulo) +├── FASE-5-VALIDACION-FINAL.md +└── FASE-6-INFORME-FINAL.md +``` + +--- + +## 8. PROXIMOS PASOS + +### Para FASE 2 (Validacion del Plan): + +- [ ] Verificar que todos los modulos de FASE 0 estan cubiertos +- [ ] Validar que dependencias estan en orden correcto +- [ ] Confirmar que templates cumplen estandar SCRUM +- [ ] Identificar gaps o ambiguedades +- [ ] Proponer ajustes si necesario + +### Para FASE 3 (Refinacion): + +- [ ] Incorporar feedback de validacion +- [ ] Resolver ambiguedades identificadas +- [ ] Producir plan final ejecutable + +### Para FASE 4 (Ejecucion): + +- [ ] Ejecutar analisis de base (primer modulo) +- [ ] Validar estructura antes de continuar +- [ ] Proceder con modulos siguientes + +--- + +**Generado:** 2026-01-04 +**Version:** 1.0 +**Estado:** COMPLETADO +**Siguiente Fase:** FASE 2 - Validacion del Plan diff --git a/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-2-VALIDACION-PLAN.md b/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-2-VALIDACION-PLAN.md new file mode 100644 index 000000000..fc024d32e --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-2-VALIDACION-PLAN.md @@ -0,0 +1,219 @@ +# FASE 2: VALIDACION DEL PLAN DE ANALISIS + +**Fecha:** 2026-01-04 +**Version:** 1.0 +**Estado:** COMPLETADO +**Documentos Validados:** +- FASE-0-ANALISIS-INICIAL.md +- FASE-1-PLAN-ANALISIS.md + +--- + +## 1. CHECKLIST DE VALIDACION + +### 1.1 Cobertura de Modulos + +| Verificacion | Estado | Notas | +|--------------|--------|-------| +| Todos los modulos de FASE 0 en el plan | OK | 9/9 modulos cubiertos | +| Modulos prioritarios identificados | OK | base, product, account, stock, purchase, sale, hr, crm, project | +| Orden respeta dependencias | OK | Bottom-up verificado | +| No hay modulos huerfanos | OK | Todos tienen dependencias claras | + +**Modulos verificados:** + +| Modulo | En FASE 0 | En FASE 1 | Orden Correcto | +|--------|-----------|-----------|----------------| +| base | Si | Si (1) | OK - sin deps | +| product | Si | Si (2) | OK - deps: base | +| account | Si | Si (3) | OK - deps: base, product | +| stock | Si | Si (4) | OK - deps: base, product | +| purchase | Si | Si (5) | OK - deps: account | +| sale | Si | Si (6) | OK - deps: account, product | +| hr | Si | Si (7) | OK - independiente | +| crm | Si | Si (8) | OK - independiente | +| project | Si | Si (9) | OK - deps: analytic | + +### 1.2 Validacion de Dependencias + +| Verificacion | Estado | Notas | +|--------------|--------|-------| +| Grafo de dependencias correcto | OK | Coincide con manifests analizados | +| Sin ciclos de dependencia | OK | Grafo aciclico (DAG) | +| Dependencias implicitas consideradas | OK | Via Many2one, One2many | +| Orden permite actualizacion retroactiva | OK | Documentado en plan | + +### 1.3 Validacion de Estructura de Documentacion + +| Verificacion | Estado | Notas | +|--------------|--------|-------| +| Templates definidos | OK | MOD, MODELO, FLUJO | +| Templates siguen estandar SCRUM | OK | Estructura clara, secciones logicas | +| Ubicacion de archivos clara | OK | Carpetas 02, 03, 04, 05, 90 | +| Documentos transversales definidos | OK | MAPA-DEPS, INVENTARIO, CLASIFICACION | + +### 1.4 Validacion de Puntos de Control + +| Verificacion | Estado | Notas | +|--------------|--------|-------| +| Puntos de validacion definidos | OK | V1-V9 para cada modulo | +| Checklist por modulo existe | OK | Template de checklist incluido | +| Acciones correctivas definidas | OK | Actualizacion retroactiva | +| Criterios de exito claros | OK | Cuantitativos y cualitativos | + +--- + +## 2. GAPS IDENTIFICADOS + +### 2.1 Gaps Menores (No Bloquean) + +| ID | Gap | Severidad | Propuesta de Solucion | +|----|-----|-----------|----------------------| +| G1 | Modulo `analytic` no en lista prioritaria pero es dependencia de project | Baja | Documentar junto con project o como anexo | +| G2 | Modulos auxiliares (mail, web, resource) no documentados | Baja | Mencionar en dependencias pero no documentar completo | +| G3 | Plantilla FLUJO asume estados, no todos los modulos tienen | Baja | Marcar N/A si modulo no tiene workflow | + +### 2.2 Gaps Potenciales (Monitorear) + +| ID | Gap | Riesgo | Mitigacion | +|----|-----|--------|------------| +| GP1 | 600 modulos totales, solo 9 documentados | Aceptable | Foco en prioritarios, resto en inventario | +| GP2 | Actualizaciones retroactivas pueden crecer | Medio | Proceso definido en FASE 1, seguir | +| GP3 | Esfuerzo estimado alto (245-302 tareas) | Medio | Ejecutar por fases, validar iterativamente | + +--- + +## 3. PROPUESTAS DE AJUSTE + +### 3.1 Ajustes Requeridos + +| ID | Ajuste | Aplicar en | +|----|--------|------------| +| A1 | Agregar seccion "N/A" en template FLUJO para modulos sin estados | FASE-3 | +| A2 | Documentar `analytic` como modulo auxiliar antes de `project` | FASE-3 | +| A3 | Agregar columna "Tiene Workflow" en orden de ejecucion | FASE-3 | + +### 3.2 Ajustes Opcionales (Nice-to-have) + +| ID | Ajuste | Beneficio | +|----|--------|-----------| +| AO1 | Agregar template para modulos auxiliares (mas ligero) | Reduce esfuerzo | +| AO2 | Crear _MAP.md por carpeta al finalizar | Navegacion | +| AO3 | Agregar diagramas Mermaid en flujos | Visualizacion | + +--- + +## 4. VALIDACION DE TEMPLATES + +### 4.1 Template MOD-[modulo].md + +| Seccion | Requerida | Presente | Correcta | +|---------|-----------|----------|----------| +| Metadata (nombre, version, categoria) | Si | Si | OK | +| Descripcion general | Si | Si | OK | +| Dependencias explicitas | Si | Si | OK | +| Dependencias implicitas | Si | Si | OK | +| Modelos principales | Si | Si | OK | +| Integraciones | Si | Si | OK | +| Referencias | Si | Si | OK | + +**Veredicto:** APROBADO + +### 4.2 Template MODELO-[modulo].md + +| Seccion | Requerida | Presente | Correcta | +|---------|-----------|----------|----------| +| Indice de modelos | Si | Si | OK | +| Campos por modelo | Si | Si | OK | +| Tipos de datos | Si | Si | OK | +| FK/Relaciones | Si | Si | OK | +| Constraints | Si | Si | OK | +| Campos computed | Si | Si | OK | +| Diagrama ER | Si | Si | OK | + +**Veredicto:** APROBADO + +### 4.3 Template FLUJO-[modulo].md + +| Seccion | Requerida | Presente | Correcta | +|---------|-----------|----------|----------| +| Estados | Condicional | Si | OK | +| Diagrama transiciones | Condicional | Si | OK | +| Metodos de transicion | Condicional | Si | OK | +| Reglas de negocio | Si | Si | OK | +| Acciones automaticas | Si | Si | OK | +| Permisos por estado | Condicional | Si | OK | + +**Veredicto:** APROBADO (agregar seccion N/A para modulos sin workflow) + +--- + +## 5. METRICAS DE VALIDACION + +### Cobertura + +| Metrica | Valor Esperado | Valor Actual | Estado | +|---------|----------------|--------------|--------| +| Modulos en plan vs FASE 0 | 9/9 | 9/9 | OK | +| Templates definidos | 3 | 3 | OK | +| Puntos validacion | 9 | 9 | OK | +| Carpetas destino | 5 | 5 | OK | + +### Coherencia + +| Metrica | Valor Esperado | Valor Actual | Estado | +|---------|----------------|--------------|--------| +| Orden respeta dependencias | 100% | 100% | OK | +| Templates con secciones obligatorias | 100% | 100% | OK | +| Gaps criticos | 0 | 0 | OK | +| Gaps menores | < 5 | 3 | OK | + +--- + +## 6. DECISION FINAL + +### Estado: **APROBADO CON AJUSTES MENORES** + +### Justificacion + +1. **Cobertura completa**: Los 9 modulos prioritarios estan cubiertos +2. **Orden correcto**: Las dependencias se respetan en la secuencia +3. **Templates validos**: Cumplen estandar y son ejecutables +4. **Gaps manejables**: Solo 3 gaps menores, con soluciones claras +5. **Plan ejecutable**: Puntos de validacion y criterios definidos + +### Ajustes a Incorporar en FASE 3 + +1. Agregar opcion N/A en template FLUJO +2. Incluir `analytic` como modulo auxiliar antes de `project` +3. Agregar columna "Tiene Workflow" en tabla de orden + +### Recomendaciones + +1. Iniciar FASE 4 con `base` para validar templates en la practica +2. Ajustar templates segun hallazgos del primer modulo +3. Mantener tracking actualizado por fase + +--- + +## 7. PROXIMOS PASOS + +### Para FASE 3 (Refinacion): + +- [ ] Incorporar ajustes A1, A2, A3 +- [ ] Generar FASE-3-PLAN-REFINADO-FINAL.md +- [ ] Confirmar que plan esta listo para ejecucion + +### Para FASE 4 (Ejecucion): + +- [ ] Comenzar con modulo `base` +- [ ] Generar MOD-base.md, MODELO-base.md, FLUJO-base.md +- [ ] Validar estructura antes de continuar con `product` + +--- + +**Generado:** 2026-01-04 +**Version:** 1.0 +**Estado:** COMPLETADO +**Decision:** APROBADO CON AJUSTES MENORES +**Siguiente Fase:** FASE 3 - Refinacion del Plan diff --git a/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-3-PLAN-REFINADO-FINAL.md b/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-3-PLAN-REFINADO-FINAL.md new file mode 100644 index 000000000..299cd031f --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-3-PLAN-REFINADO-FINAL.md @@ -0,0 +1,446 @@ +# FASE 3: PLAN REFINADO FINAL DE ANALISIS ODOO 18.0 + +**Fecha:** 2026-01-04 +**Version:** 1.0 +**Estado:** COMPLETADO +**Basado en:** FASE-1 + Ajustes de FASE-2 + +--- + +## RESUMEN DE AJUSTES INCORPORADOS + +| ID | Ajuste | Estado | +|----|--------|--------| +| A1 | Seccion N/A en template FLUJO | Incorporado | +| A2 | `analytic` como modulo auxiliar | Incorporado | +| A3 | Columna "Tiene Workflow" | Incorporado | + +--- + +## 1. ORDEN DE EJECUCION REFINADO + +### Secuencia Final de Analisis + +| Orden | Modulo | Tipo | Dependencias | Complejidad | Workflow | Docs | +|-------|--------|------|--------------|-------------|----------|------| +| 1 | **base** | Core | Ninguna | ALTA | Parcial | 3 | +| 2 | **product** | Business | base | MEDIA-ALTA | No | 2* | +| 3 | **account** | Business | base, product | ALTA | Si | 3 | +| 4 | **stock** | Business | base, product | MEDIA-ALTA | Si | 3 | +| 5 | **purchase** | Business | account | MEDIA-BAJA | Si | 3 | +| 6 | **sale** | Business | account, product | MEDIA | Si | 3 | +| 7 | **hr** | Business | base | MEDIA-ALTA | Parcial | 3 | +| 8 | **crm** | Business | base | MEDIA | Si | 3 | +| 8.5 | **analytic** | Auxiliar | base | BAJA | No | 1** | +| 9 | **project** | Business | analytic | MEDIA-ALTA | Si | 3 | + +*Notas:* +- `*` = Sin FLUJO (modulo sin workflow de estados) +- `**` = Solo MOD (modulo auxiliar, documentacion ligera) + +### Tipos de Modulo + +| Tipo | Descripcion | Documentos | +|------|-------------|------------| +| Core | Kernel fundamental | MOD + MODELO + FLUJO | +| Business | Aplicacion de negocio | MOD + MODELO + FLUJO (si workflow) | +| Auxiliar | Soporte para otros | Solo MOD (ligero) | + +--- + +## 2. DOCUMENTOS A GENERAR + +### Por Modulo + +| Modulo | MOD | MODELO | FLUJO | Total | +|--------|-----|--------|-------|-------| +| base | Si | Si | Si | 3 | +| product | Si | Si | N/A | 2 | +| account | Si | Si | Si | 3 | +| stock | Si | Si | Si | 3 | +| purchase | Si | Si | Si | 3 | +| sale | Si | Si | Si | 3 | +| hr | Si | Si | Si | 3 | +| crm | Si | Si | Si | 3 | +| analytic | Si | N/A | N/A | 1 | +| project | Si | Si | Si | 3 | +| **TOTAL** | 10 | 9 | 8 | **27** | + +### Transversales + +| Documento | Ubicacion | Prioridad | +|-----------|-----------|-----------| +| MAPA-DEPENDENCIAS-MODULOS.md | 90-transversal/ | Alta | +| INVENTARIO-MODULOS-ODOO.md | 90-transversal/ | Media | +| CLASIFICACION-MODULOS.md | 90-transversal/ | Media | +| API-XMLRPC.md | 05-apis-integraciones/ | Baja | + +--- + +## 3. TEMPLATES REFINADOS + +### Template MOD-[modulo].md (Sin cambios) + +Ubicacion: `02-modulos-business/` o `01-modulos-core/` + +```markdown +# Modulo: [Nombre] + +**Odoo Module:** [nombre_tecnico] +**Version:** [version] +**Categoria:** [categoria] +**Es Aplicacion:** [Si/No] + +--- + +## 1. Descripcion General +[descripcion] + +## 2. Dependencias +### Explicitas +[tabla] +### Implicitas +[tabla] + +## 3. Modelos Principales +[tabla] + +## 4. Integraciones +[descripcion] + +## 5. Referencias +- Fuente: `addons/[modulo]/` +``` + +### Template MODELO-[modulo].md (Sin cambios) + +Ubicacion: `03-modelado-datos/` + +```markdown +# Modelo de Datos: [Nombre] + +## 1. Indice de Modelos +[tabla] + +## 2. Detalle por Modelo +### [modelo] +- Campos +- Computed +- Constraints +- Relaciones + +## 3. Diagrama ER +[diagrama] +``` + +### Template FLUJO-[modulo].md (REFINADO) + +Ubicacion: `04-logica-negocio/` + +```markdown +# Flujo de Negocio: [Nombre] + +**Aplica Workflow:** [Si/No/Parcial] + +--- + +## CASO: MODULO CON WORKFLOW + +## 1. Estados +[tabla estados] + +## 2. Diagrama de Transiciones +[diagrama] + +## 3. Metodos de Transicion +[tabla metodos] + +## 4. Reglas de Negocio +[tabla reglas] + +## 5. Acciones Automaticas +[tabla acciones] + +## 6. Permisos por Estado +[tabla permisos] + +--- + +## CASO: MODULO SIN WORKFLOW (N/A) + +## 1. Nota +Este modulo no implementa un workflow de estados tradicional. +Los modelos principales operan sin transiciones de estado. + +## 2. Logica de Negocio Relevante +[descripcion de constraints, validaciones, metodos importantes] + +## 3. Integraciones +[como se integra con modulos que si tienen workflow] +``` + +### Template MOD-AUXILIAR-[modulo].md (NUEVO) + +Ubicacion: `02-modulos-business/auxiliares/` + +```markdown +# Modulo Auxiliar: [Nombre] + +**Odoo Module:** [nombre_tecnico] +**Tipo:** Auxiliar/Soporte +**Usado por:** [lista de modulos que dependen de este] + +--- + +## 1. Proposito +[descripcion breve] + +## 2. Modelos Principales +| Modelo | Descripcion | +|--------|-------------| +| [modelo] | [desc] | + +## 3. Dependencias +[tabla simple] + +## 4. Referencias +- Fuente: `addons/[modulo]/` +``` + +--- + +## 4. FASES DE EJECUCION + +### FASE 4A: Fundamentos (1 modulo) + +| Paso | Modulo | Acciones | +|------|--------|----------| +| 4A.1 | base | Analizar manifest, models/, views/ | +| 4A.2 | base | Generar MOD-base.md | +| 4A.3 | base | Generar MODELO-base.md | +| 4A.4 | base | Generar FLUJO-base.md | +| 4A.5 | - | Validar estructura, ajustar si necesario | + +### FASE 4B: Catalogo (1 modulo) + +| Paso | Modulo | Acciones | +|------|--------|----------| +| 4B.1 | product | Analizar, generar MOD + MODELO | +| 4B.2 | - | Validar refs a base | + +### FASE 4C: Contabilidad y Logistica (2 modulos) + +| Paso | Modulo | Acciones | +|------|--------|----------| +| 4C.1 | account | Analizar, generar 3 docs | +| 4C.2 | stock | Analizar, generar 3 docs | +| 4C.3 | - | Validar refs cruzadas | + +### FASE 4D: Transacciones (2 modulos) + +| Paso | Modulo | Acciones | +|------|--------|----------| +| 4D.1 | purchase | Analizar, generar 3 docs | +| 4D.2 | sale | Analizar, generar 3 docs | +| 4D.3 | - | Validar integracion | + +### FASE 4E: Recursos y Proyectos (4 modulos) + +| Paso | Modulo | Acciones | +|------|--------|----------| +| 4E.1 | hr | Analizar, generar 3 docs | +| 4E.2 | crm | Analizar, generar 3 docs | +| 4E.3 | analytic | Documentacion auxiliar ligera | +| 4E.4 | project | Analizar, generar 3 docs | + +### FASE 4F: Transversales + +| Paso | Documento | Acciones | +|------|-----------|----------| +| 4F.1 | MAPA-DEPENDENCIAS | Compilar de todos los modulos | +| 4F.2 | INVENTARIO | Listar 600 modulos | +| 4F.3 | CLASIFICACION | Categorizar por dominio | + +--- + +## 5. PUNTOS DE VALIDACION + +### Validacion Post-Modulo + +| Checkpoint | Modulo | Verificar | +|------------|--------|-----------| +| CP1 | base | Templates funcionan, estructura OK | +| CP2 | product | Refs a base correctas | +| CP3 | account | Refs a base, product OK | +| CP4 | stock | Refs a base, product OK | +| CP5 | purchase | Refs a account OK | +| CP6 | sale | Refs a account, product OK | +| CP7 | hr | Estructura independiente OK | +| CP8 | crm | Estructura independiente OK | +| CP9 | analytic | Doc auxiliar OK | +| CP10 | project | Refs a analytic OK | + +### Validacion Final (FASE 5) + +| Item | Criterio | +|------|----------| +| Cobertura | 10/10 modulos documentados | +| Referencias | 0 referencias rotas | +| Coherencia | Dependencias bidireccionales OK | +| Formato | 100% cumple templates | + +--- + +## 6. CRITERIOS DE EXITO + +### Por Documento + +| Tipo | Criterio | +|------|----------| +| MOD | Descripcion clara, deps completas, modelos listados | +| MODELO | Todos los campos con tipo, FKs identificadas, diagrama ER | +| FLUJO | Estados completos, transiciones documentadas, reglas claras | + +### Por Modulo + +| Criterio | Objetivo | +|----------|----------| +| % Modelos documentados | 100% principales | +| % Campos documentados | >= 90% | +| Referencias correctas | 100% | +| Comprensible sin codigo | Si | + +### Global + +| Metrica | Objetivo | +|---------|----------| +| Documentos generados | 27 + transversales | +| Modulos cubiertos | 10 (9 business + 1 auxiliar) | +| Tiempo estimado | Por definir en ejecucion | + +--- + +## 7. ENTREGABLES FINALES + +### Estructura de Carpetas + +``` +/home/isem/workspace-v1/shared/knowledge-base/reference/odoo/docs/ +├── _MAP.md +├── README.md +├── _analisis/ +│ ├── FASE-0-ANALISIS-INICIAL.md +│ ├── FASE-1-PLAN-ANALISIS.md +│ ├── FASE-2-VALIDACION-PLAN.md +│ ├── FASE-3-PLAN-REFINADO-FINAL.md (este) +│ ├── MODULO-*-CRUDO.md (intermedios) +│ ├── VALIDACION-DEPS-*.md (por modulo) +│ ├── FASE-5-VALIDACION-FINAL.md +│ └── FASE-6-INFORME-FINAL.md +├── 00-vision-general/ +│ ├── _MAP.md +│ └── ARQUITECTURA-ODOO.md +├── 01-modulos-core/ +│ ├── _MAP.md +│ └── MOD-base.md +├── 02-modulos-business/ +│ ├── _MAP.md +│ ├── MOD-product.md +│ ├── MOD-account.md +│ ├── MOD-stock.md +│ ├── MOD-purchase.md +│ ├── MOD-sale.md +│ ├── MOD-hr.md +│ ├── MOD-crm.md +│ ├── MOD-project.md +│ └── auxiliares/ +│ └── MOD-analytic.md +├── 03-modelado-datos/ +│ ├── _MAP.md +│ ├── MODELO-base.md +│ ├── MODELO-product.md +│ ├── MODELO-account.md +│ ├── MODELO-stock.md +│ ├── MODELO-purchase.md +│ ├── MODELO-sale.md +│ ├── MODELO-hr.md +│ ├── MODELO-crm.md +│ └── MODELO-project.md +├── 04-logica-negocio/ +│ ├── _MAP.md +│ ├── FLUJO-base.md +│ ├── FLUJO-account.md (contabilidad) +│ ├── FLUJO-stock.md (inventario) +│ ├── FLUJO-purchase.md (compras) +│ ├── FLUJO-sale.md (ventas) +│ ├── FLUJO-hr.md +│ ├── FLUJO-crm.md +│ └── FLUJO-project.md +├── 05-apis-integraciones/ +│ ├── _MAP.md +│ └── API-XMLRPC.md +└── 90-transversal/ + ├── _MAP.md + ├── MAPA-DEPENDENCIAS-MODULOS.md + ├── INVENTARIO-MODULOS-ODOO.md + └── CLASIFICACION-MODULOS.md +``` + +--- + +## 8. PROXIMOS PASOS + +### Iniciar FASE 4 (Ejecucion) + +1. **Comenzar con `base`:** + - Leer `addons/base/__manifest__.py` + - Explorar `addons/base/models/` + - Generar MOD-base.md + - Generar MODELO-base.md + - Generar FLUJO-base.md + +2. **Validar en CP1:** + - Verificar que estructura funciona + - Ajustar templates si necesario + +3. **Continuar secuencialmente:** + - product → account → stock → ... + +### Comandos Utiles + +```bash +# Ver manifest de un modulo +cat /home/isem/workspace-v1/shared/knowledge-base/reference/odoo/odoo-18.0/addons/base/__manifest__.py + +# Listar modelos +ls /home/isem/workspace-v1/shared/knowledge-base/reference/odoo/odoo-18.0/addons/base/models/ + +# Buscar estados +grep -r "state = fields.Selection" addons/base/models/ + +# Buscar transiciones +grep -r "def action_" addons/base/models/ +``` + +--- + +## 9. ESTADO DEL PLAN + +| Fase | Estado | Fecha | +|------|--------|-------| +| FASE 0 | COMPLETADO | 2026-01-04 | +| FASE 1 | COMPLETADO | 2026-01-04 | +| FASE 2 | COMPLETADO | 2026-01-04 | +| FASE 3 | COMPLETADO | 2026-01-04 | +| FASE 4 | PENDIENTE | - | +| FASE 5 | PENDIENTE | - | +| FASE 6 | PENDIENTE | - | + +**El plan esta LISTO PARA EJECUCION.** + +--- + +**Generado:** 2026-01-04 +**Version:** 1.0 +**Estado:** COMPLETADO - LISTO PARA FASE 4 +**Aprobacion:** Plan validado y refinado diff --git a/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-5-REPORTE-FINAL.md b/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-5-REPORTE-FINAL.md new file mode 100644 index 000000000..650a02a38 --- /dev/null +++ b/shared/knowledge-base/reference/odoo/docs/_analisis/FASE-5-REPORTE-FINAL.md @@ -0,0 +1,196 @@ +# FASE 5: Reporte Final de Documentacion + +**Proyecto:** P00 - Reestructuracion Knowledge Base Odoo +**Fecha Completado:** 2026-01-04 +**Estado:** COMPLETADO + +--- + +## 1. Resumen Ejecutivo + +Se completo exitosamente la documentacion estructurada de los 10 modulos prioritarios de Odoo 18.0, generando 30 documentos organizados segun la arquitectura SIMCO. + +--- + +## 2. Documentos Generados + +### 2.1 Por Tipo + +| Tipo | Cantidad | Descripcion | +|------|----------|-------------| +| MOD-*.md | 10 | Vision general del modulo | +| MODELO-*.md | 10 | Modelos de datos y campos | +| FLUJO-*.md | 7 | Flujos de trabajo y estados | +| Transversal | 3 | Documentacion cruzada | +| **TOTAL** | **30** | | + +### 2.2 Por Ubicacion + +| Carpeta | Archivos | +|---------|----------| +| 01-modulos-core | MOD-base.md, MOD-product.md | +| 02-modulos-business | MOD-account.md, MOD-analytic.md, MOD-crm.md, MOD-hr.md, MOD-project.md, MOD-purchase.md, MOD-sale.md, MOD-stock.md | +| 03-modelado-datos | MODELO-*.md (10 archivos) | +| 04-logica-negocio | FLUJO-*.md (7 archivos) | +| 90-transversal | MAPA-DEPENDENCIAS-MODULOS.md, INVENTARIO-MODULOS-ODOO.md, CLASIFICACION-MODULOS.md | + +--- + +## 3. Modulos Documentados + +| # | Modulo | MOD | MODELO | FLUJO | Notas | +|---|--------|-----|--------|-------|-------| +| 1 | base | OK | OK | OK | Fundamento ORM | +| 2 | product | OK | OK | - | Sin workflow (datos maestros) | +| 3 | account | OK | OK | OK | Contabilidad | +| 4 | stock | OK | OK | OK | Inventario | +| 5 | purchase | OK | OK | OK | Compras | +| 6 | sale | OK | OK | OK | Ventas | +| 7 | hr | OK | OK | - | Sin workflow (configuracion) | +| 8 | crm | OK | OK | OK | Lead scoring incluido | +| 9 | analytic | OK | OK | - | Sin workflow (auxiliar) | +| 10 | project | OK | OK | OK | Tareas y dependencias | + +--- + +## 4. Cobertura por Modulo + +``` +base: [MOD] [MODELO] [FLUJO] 100% +product: [MOD] [MODELO] [ --- ] 67% (sin workflow) +account: [MOD] [MODELO] [FLUJO] 100% +stock: [MOD] [MODELO] [FLUJO] 100% +purchase: [MOD] [MODELO] [FLUJO] 100% +sale: [MOD] [MODELO] [FLUJO] 100% +hr: [MOD] [MODELO] [ --- ] 67% (sin workflow) +crm: [MOD] [MODELO] [FLUJO] 100% +analytic: [MOD] [MODELO] [ --- ] 67% (sin workflow) +project: [MOD] [MODELO] [FLUJO] 100% +``` + +--- + +## 5. Fases Completadas + +| Fase | Descripcion | Estado | +|------|-------------|--------| +| F0 | Analisis inicial | COMPLETADO | +| F1 | Plan de analisis | COMPLETADO | +| F2 | Validacion del plan | COMPLETADO | +| F3 | Plan refinado final | COMPLETADO | +| F4A | Documentar BASE | COMPLETADO | +| F4B | Documentar PRODUCT | COMPLETADO | +| F4C | Documentar ACCOUNT y STOCK | COMPLETADO | +| F4D | Documentar PURCHASE y SALE | COMPLETADO | +| F4E | Documentar HR, CRM, ANALYTIC, PROJECT | COMPLETADO | +| F4F | Documentos transversales | COMPLETADO | +| F5 | Validacion final | COMPLETADO | + +--- + +## 6. Estructura Final de Directorios + +``` +docs/ +├── 00-vision-general/ (pendiente futuro) +├── 01-modulos-core/ +│ ├── MOD-base.md +│ └── MOD-product.md +├── 02-modulos-business/ +│ ├── MOD-account.md +│ ├── MOD-analytic.md +│ ├── MOD-crm.md +│ ├── MOD-hr.md +│ ├── MOD-project.md +│ ├── MOD-purchase.md +│ ├── MOD-sale.md +│ └── MOD-stock.md +├── 03-modelado-datos/ +│ ├── MODELO-account.md +│ ├── MODELO-analytic.md +│ ├── MODELO-base.md +│ ├── MODELO-crm.md +│ ├── MODELO-hr.md +│ ├── MODELO-product.md +│ ├── MODELO-project.md +│ ├── MODELO-purchase.md +│ ├── MODELO-sale.md +│ └── MODELO-stock.md +├── 04-logica-negocio/ +│ ├── FLUJO-account.md +│ ├── FLUJO-base.md +│ ├── FLUJO-crm.md +│ ├── FLUJO-project.md +│ ├── FLUJO-purchase.md +│ ├── FLUJO-sale.md +│ └── FLUJO-stock.md +├── 05-apis-integraciones/ (pendiente futuro) +├── 90-transversal/ +│ ├── CLASIFICACION-MODULOS.md +│ ├── INVENTARIO-MODULOS-ODOO.md +│ └── MAPA-DEPENDENCIAS-MODULOS.md +└── _analisis/ + ├── FASE-0-ANALISIS-INICIAL.md + ├── FASE-1-PLAN-ANALISIS.md + ├── FASE-2-VALIDACION-PLAN.md + ├── FASE-3-PLAN-REFINADO-FINAL.md + └── FASE-5-REPORTE-FINAL.md +``` + +--- + +## 7. Metricas de Calidad + +| Metrica | Valor | +|---------|-------| +| Lineas totales documentacion | ~4,500+ | +| Diagramas ASCII incluidos | 25+ | +| Tablas de referencia | 100+ | +| Estados documentados | 30+ | +| Modelos documentados | 60+ | + +--- + +## 8. Proximos Pasos Sugeridos + +### Alta Prioridad +1. Documentar modulo `mrp` (manufactura) +2. Completar carpeta `00-vision-general` con README principal +3. Documentar integraciones en `05-apis-integraciones` + +### Media Prioridad +1. Documentar modulos secundarios (hr_expense, hr_timesheet) +2. Agregar ejemplos de codigo a los FLUJO-*.md +3. Crear indice general de navegacion + +### Baja Prioridad +1. Documentar modulos website/portal +2. Agregar diagramas Mermaid adicionales +3. Traducir documentacion a ingles + +--- + +## 9. Notas Tecnicas + +- Los modulos `product`, `hr` y `analytic` no tienen FLUJO porque son modelos de configuracion/datos maestros sin workflow de estados +- El modulo `crm` incluye documentacion del Predictive Lead Scoring (PLS) basado en Naive Bayes +- Los documentos transversales permiten navegacion rapida entre modulos relacionados + +--- + +## 10. Validacion Final + +``` +[OK] 10/10 modulos prioritarios documentados +[OK] 30/30 documentos generados +[OK] Estructura de carpetas correcta +[OK] Sin errores de sintaxis markdown +[OK] Referencias cruzadas consistentes +``` + +**PROYECTO P00 COMPLETADO EXITOSAMENTE** + +--- + +**Generado:** 2026-01-04 +**Herramienta:** Claude Code diff --git a/core/standards/ESTANDAR-ESTRUCTURA-DOCUMENTACION.md b/shared/knowledge-base/standards/ESTANDAR-ESTRUCTURA-DOCUMENTACION.md similarity index 100% rename from core/standards/ESTANDAR-ESTRUCTURA-DOCUMENTACION.md rename to shared/knowledge-base/standards/ESTANDAR-ESTRUCTURA-DOCUMENTACION.md diff --git a/shared/libs/CATALOG-INDEX.yml b/shared/libs/CATALOG-INDEX.yml deleted file mode 100644 index 97bca8adb..000000000 --- a/shared/libs/CATALOG-INDEX.yml +++ /dev/null @@ -1,359 +0,0 @@ -# ═══════════════════════════════════════════════════════════════════════════════ -# ÍNDICE DEL CATÁLOGO DE FUNCIONALIDADES REUTILIZABLES -# ═══════════════════════════════════════════════════════════════════════════════ -# -# Versión: 1.0.0 -# Fecha: 2025-12-08 -# Propósito: Índice máquina-readable para búsqueda rápida de funcionalidades -# -# USO: -# Los agentes consultan este archivo ANTES de implementar funcionalidades comunes. -# Usar: grep -i "{funcionalidad}" @CATALOG_INDEX -# -# ═══════════════════════════════════════════════════════════════════════════════ - -version: "1.0.0" -fecha_actualizacion: "2025-12-08" -total_funcionalidades: 8 - -# ───────────────────────────────────────────────────────────────────────────────── -# ÍNDICE DE FUNCIONALIDADES -# ───────────────────────────────────────────────────────────────────────────────── - -funcionalidades: - - # ═══════════════════════════════════════════════════════════════════ - # AUTENTICACIÓN Y SEGURIDAD - # ═══════════════════════════════════════════════════════════════════ - - auth: - nombre: "Autenticación y Autorización" - path: "core/catalog/auth/" - alias: "@CATALOG_AUTH" - estado: "production-ready" # production-ready | documentando | pendiente - origen: "projects/gamilit" - version: "1.0.0" - stack: - - "NestJS" - - "JWT" - - "Passport" - - "bcrypt" - - "TypeORM" - keywords: - - "login" - - "registro" - - "jwt" - - "token" - - "password" - - "oauth" - - "social-login" - - "autenticacion" - - "authorization" - - "guard" - caracteristicas: - - "JWT access + refresh tokens" - - "Password hashing con bcrypt" - - "Multiple OAuth providers" - - "Auth attempts tracking" - - "Password recovery" - dependencias_npm: - - "@nestjs/jwt" - - "@nestjs/passport" - - "passport-jwt" - - "bcrypt" - tablas_requeridas: - - "auth.users" - - "auth.user_sessions" - - "auth.auth_attempts" - - session-management: - nombre: "Gestión de Sesiones" - path: "core/catalog/session-management/" - alias: "@CATALOG_SESSION" - estado: "production-ready" - origen: "projects/gamilit" - version: "1.0.0" - stack: - - "NestJS" - - "TypeORM" - - "PostgreSQL" - keywords: - - "sesion" - - "session" - - "logout" - - "dispositivo" - - "device" - - "concurrent" - - "max-sessions" - caracteristicas: - - "Máximo N sesiones concurrentes" - - "Auto-cleanup de sesiones expiradas" - - "Tracking de dispositivos" - - "Logout de sesión específica" - - "Logout de todas las sesiones" - dependencias_npm: - - "typeorm" - tablas_requeridas: - - "auth.user_sessions" - - rate-limiting: - nombre: "Limitación de Tasa (Rate Limiting)" - path: "core/catalog/rate-limiting/" - alias: "@CATALOG_RATELIMIT" - estado: "production-ready" - origen: "projects/gamilit" - version: "1.0.0" - stack: - - "NestJS" - - "Redis (opcional)" - keywords: - - "rate-limit" - - "throttle" - - "429" - - "too-many-requests" - - "abuse" - - "ddos" - - "limite" - caracteristicas: - - "Rate limiting por usuario/IP" - - "Configuración por endpoint" - - "Headers de límite estándar" - - "Respuestas 429 con retry-after" - dependencias_npm: - - "@nestjs/throttler" - tablas_requeridas: [] - - # ═══════════════════════════════════════════════════════════════════ - # COMUNICACIÓN Y NOTIFICACIONES - # ═══════════════════════════════════════════════════════════════════ - - notifications: - nombre: "Sistema de Notificaciones" - path: "core/catalog/notifications/" - alias: "@CATALOG_NOTIFY" - estado: "production-ready" - origen: "projects/gamilit" - version: "1.0.0" - stack: - - "NestJS" - - "TypeORM" - - "Nodemailer" - - "FCM (opcional)" - keywords: - - "notificacion" - - "notification" - - "email" - - "push" - - "in-app" - - "alerta" - - "mensaje" - caracteristicas: - - "Notificaciones email" - - "Notificaciones in-app" - - "Push notifications (FCM)" - - "Preferencias de usuario" - - "Historial de notificaciones" - - "Templates de email" - dependencias_npm: - - "nodemailer" - - "@nestjs/mailer" - - "firebase-admin (opcional)" - tablas_requeridas: - - "notifications.notifications" - - "notifications.notification_preferences" - - "notifications.notification_templates" - - websocket: - nombre: "Comunicación WebSocket" - path: "core/catalog/websocket/" - alias: "@CATALOG_WS" - estado: "production-ready" - origen: "projects/trading-platform" - version: "1.0.0" - stack: - - "NestJS" - - "Socket.io" - keywords: - - "websocket" - - "socket" - - "realtime" - - "tiempo-real" - - "streaming" - - "live" - - "push" - caracteristicas: - - "Conexiones WebSocket" - - "Rooms/canales" - - "Autenticación de socket" - - "Broadcasting" - - "Heartbeat/ping" - dependencias_npm: - - "@nestjs/websockets" - - "@nestjs/platform-socket.io" - - "socket.io" - tablas_requeridas: [] - - # ═══════════════════════════════════════════════════════════════════ - # ARQUITECTURA MULTI-TENANT - # ═══════════════════════════════════════════════════════════════════ - - multi-tenancy: - nombre: "Soporte Multi-Tenant" - path: "core/catalog/multi-tenancy/" - alias: "@CATALOG_TENANT" - estado: "production-ready" - origen: "projects/gamilit" - version: "1.0.0" - stack: - - "PostgreSQL" - - "RLS" - - "NestJS" - keywords: - - "tenant" - - "multi-tenant" - - "organization" - - "empresa" - - "aislamiento" - - "rls" - - "row-level-security" - caracteristicas: - - "Aislamiento por tenant" - - "Row Level Security (RLS)" - - "Tenant context en requests" - - "Configuración por tenant" - dependencias_npm: - - "typeorm" - tablas_requeridas: - - "core.organizations" - - "core.organization_members" - - # ═══════════════════════════════════════════════════════════════════ - # FEATURE FLAGS Y CONFIGURACIÓN - # ═══════════════════════════════════════════════════════════════════ - - feature-flags: - nombre: "Feature Flags Dinámicos" - path: "core/catalog/feature-flags/" - alias: "@CATALOG_FLAGS" - estado: "production-ready" - origen: "projects/gamilit" - version: "1.0.0" - stack: - - "NestJS" - - "TypeORM" - - "PostgreSQL" - keywords: - - "feature-flag" - - "toggle" - - "feature" - - "bandera" - - "a/b-test" - - "rollout" - - "configuracion" - caracteristicas: - - "Flags por ambiente" - - "Flags por usuario/rol" - - "Activación gradual (rollout)" - - "Cache de flags" - - "UI de administración" - dependencias_npm: - - "typeorm" - tablas_requeridas: - - "config.feature_flags" - - "config.feature_flag_rules" - - # ═══════════════════════════════════════════════════════════════════ - # PAGOS E INTEGRACIONES - # ═══════════════════════════════════════════════════════════════════ - - payments: - nombre: "Integración de Pagos" - path: "core/catalog/payments/" - alias: "@CATALOG_PAYMENTS" - estado: "production-ready" - origen: "projects/trading-platform" - version: "1.0.0" - stack: - - "NestJS" - - "Stripe" - keywords: - - "pago" - - "payment" - - "stripe" - - "subscription" - - "suscripcion" - - "factura" - - "invoice" - - "checkout" - caracteristicas: - - "Integración Stripe" - - "Webhooks de pago" - - "Suscripciones" - - "Checkout sessions" - - "Historial de pagos" - dependencias_npm: - - "stripe" - tablas_requeridas: - - "billing.subscriptions" - - "billing.payments" - - "billing.invoices" - -# ───────────────────────────────────────────────────────────────────────────────── -# BÚSQUEDA RÁPIDA POR KEYWORD -# ───────────────────────────────────────────────────────────────────────────────── -# -# Para buscar funcionalidad por keyword: -# grep -i "{keyword}" core/catalog/CATALOG-INDEX.yml -# -# Ejemplos: -# grep -i "login" → auth -# grep -i "sesion" → session-management -# grep -i "429" → rate-limiting -# grep -i "email" → notifications -# grep -i "realtime" → websocket -# grep -i "tenant" → multi-tenancy -# grep -i "toggle" → feature-flags -# grep -i "stripe" → payments -# -# ───────────────────────────────────────────────────────────────────────────────── - -# ───────────────────────────────────────────────────────────────────────────────── -# INSTRUCCIONES PARA AGENTES -# ───────────────────────────────────────────────────────────────────────────────── - -instrucciones: - cuando_consultar: | - SIEMPRE consultar este índice ANTES de implementar: - - Autenticación/login - - Gestión de sesiones - - Rate limiting - - Notificaciones - - WebSockets/tiempo real - - Multi-tenancy - - Feature flags - - Pagos/suscripciones - - como_buscar: | - 1. Buscar por keyword: - grep -i "{lo que necesito}" @CATALOG_INDEX - - 2. Si encuentra match: - - Leer path indicado - - Verificar estado (preferir production-ready) - - Verificar stack compatible - - 3. Si NO encuentra: - - Proceder con implementación nueva - - Considerar agregar al catálogo después - - que_hacer_si_encuentra: | - 1. Ir a: core/catalog/{funcionalidad}/ - 2. Leer: README.md (descripción y trade-offs) - 3. Seguir: IMPLEMENTATION.md (pasos) - 4. Copiar: _reference/ (código base) - 5. Adaptar: configuración al proyecto actual - 6. Validar: ejecutar tests de referencia - -# ═══════════════════════════════════════════════════════════════════════════════ -# FIN DEL ÍNDICE -# ═══════════════════════════════════════════════════════════════════════════════ diff --git a/shared/libs/CATALOG-USAGE-TRACKING.yml b/shared/libs/CATALOG-USAGE-TRACKING.yml deleted file mode 100644 index 1406483b8..000000000 --- a/shared/libs/CATALOG-USAGE-TRACKING.yml +++ /dev/null @@ -1,133 +0,0 @@ -# CATALOG USAGE TRACKING -# Sistema: NEXUS + SIMCO v2.2.0 -# Propósito: Rastrear el uso de funcionalidades del catálogo en proyectos - -version: "1.0.0" -ultima_actualizacion: "2025-12-08" - -# ═══════════════════════════════════════════════════════════════════════════════ -# REGISTRO DE USO POR FUNCIONALIDAD -# ═══════════════════════════════════════════════════════════════════════════════ - -funcionalidades: - - auth: - proyectos_usando: - - proyecto: gamilit - fecha_adopcion: "2025-12-01" - estado: produccion - adaptaciones: "Ninguna" - - - proyecto: trading-platform - fecha_adopcion: "2025-12-05" - estado: desarrollo - adaptaciones: "OAuth extendido con más providers" - - - proyecto: erp-core - fecha_adopcion: "2025-12-03" - estado: desarrollo - adaptaciones: "Multi-tenant auth" - - total_proyectos: 3 - - session-management: - proyectos_usando: - - proyecto: gamilit - fecha_adopcion: "2025-12-01" - estado: produccion - - - proyecto: trading-platform - fecha_adopcion: "2025-12-05" - estado: desarrollo - - total_proyectos: 2 - - rate-limiting: - proyectos_usando: - - proyecto: gamilit - fecha_adopcion: "2025-12-01" - estado: produccion - - - proyecto: trading-platform - fecha_adopcion: "2025-12-06" - estado: desarrollo - - total_proyectos: 2 - - notifications: - proyectos_usando: - - proyecto: gamilit - fecha_adopcion: "2025-12-02" - estado: produccion - - total_proyectos: 1 - - websocket: - proyectos_usando: - - proyecto: trading-platform - fecha_adopcion: "2025-12-06" - estado: desarrollo - adaptaciones: "Streaming de precios Binance" - - total_proyectos: 1 - - multi-tenancy: - proyectos_usando: - - proyecto: gamilit - fecha_adopcion: "2025-12-01" - estado: produccion - - - proyecto: erp-core - fecha_adopcion: "2025-12-03" - estado: desarrollo - - total_proyectos: 2 - - feature-flags: - proyectos_usando: - - proyecto: gamilit - fecha_adopcion: "2025-12-02" - estado: produccion - - total_proyectos: 1 - - payments: - proyectos_usando: - - proyecto: trading-platform - fecha_adopcion: "2025-12-06" - estado: desarrollo - adaptaciones: "Planes de suscripción específicos" - - total_proyectos: 1 - -# ═══════════════════════════════════════════════════════════════════════════════ -# RESUMEN -# ═══════════════════════════════════════════════════════════════════════════════ - -resumen: - total_funcionalidades: 8 - funcionalidades_production_ready: 8 - total_adopciones: 14 - proyecto_mas_activo: gamilit - funcionalidad_mas_usada: auth - -# ═══════════════════════════════════════════════════════════════════════════════ -# REGISTRO DE CAMBIOS -# ═══════════════════════════════════════════════════════════════════════════════ - -changelog: - - fecha: "2025-12-08" - cambio: "Archivo de tracking creado" - autor: "NEXUS-System" - -# ═══════════════════════════════════════════════════════════════════════════════ -# INSTRUCCIONES -# ═══════════════════════════════════════════════════════════════════════════════ -# -# Cuando un proyecto adopta una funcionalidad del catálogo: -# 1. Agregar entrada en proyectos_usando de la funcionalidad -# 2. Actualizar total_proyectos -# 3. Actualizar resumen -# 4. Agregar entrada en changelog -# -# ═══════════════════════════════════════════════════════════════════════════════ diff --git a/shared/libs/README.md b/shared/libs/README.md deleted file mode 100644 index 2f193e74b..000000000 --- a/shared/libs/README.md +++ /dev/null @@ -1,469 +0,0 @@ -# CATÁLOGO DE FUNCIONALIDADES REUTILIZABLES - -**Versión:** 1.0.0 -**Fecha:** 2025-12-08 -**Sistema:** NEXUS SIMCO v3.0 - ---- - -## PROPÓSITO - -Este catálogo centraliza **código funcional probado y documentado** que puede ser reutilizado entre proyectos. Antes de implementar una funcionalidad común, los agentes DEBEN consultar este catálogo. - ---- - -## PRINCIPIO FUNDAMENTAL - -``` -╔══════════════════════════════════════════════════════════════════════╗ -║ ║ -║ ANTES DE IMPLEMENTAR, VERIFICAR SI YA EXISTE EN @CATALOG ║ -║ ║ -║ "Código probado > código nuevo" ║ -║ "Reutilizar es más rápido que reinventar" ║ -║ ║ -╚══════════════════════════════════════════════════════════════════════╝ -``` - ---- - -## ESTRUCTURA DEL CATÁLOGO - -``` -core/catalog/ -├── README.md ← ESTÁS AQUÍ -├── CATALOG-INDEX.yml # Índice máquina-readable -│ -├── auth/ # Autenticación y autorización -│ ├── README.md # Descripción, cuándo usar -│ ├── IMPLEMENTATION.md # Guía de implementación -│ └── _reference/ # Código de referencia -│ -├── session-management/ # Gestión de sesiones -│ ├── README.md -│ ├── IMPLEMENTATION.md -│ └── _reference/ -│ -├── rate-limiting/ # Limitación de tasa -│ ├── README.md -│ ├── IMPLEMENTATION.md -│ └── _reference/ -│ -├── notifications/ # Sistema de notificaciones -│ ├── README.md -│ ├── IMPLEMENTATION.md -│ └── _reference/ -│ -├── multi-tenancy/ # Soporte multi-tenant -│ ├── README.md -│ ├── IMPLEMENTATION.md -│ └── _reference/ -│ -├── feature-flags/ # Feature flags dinámicos -│ ├── README.md -│ ├── IMPLEMENTATION.md -│ └── _reference/ -│ -├── websocket/ # Comunicación en tiempo real -│ ├── README.md -│ ├── IMPLEMENTATION.md -│ └── _reference/ -│ -└── payments/ # Integración de pagos - ├── README.md - ├── IMPLEMENTATION.md - └── _reference/ -``` - ---- - -## CÓMO USAR EL CATÁLOGO - -### Para Agentes: Flujo de Verificación - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ ANTES de implementar funcionalidad común: │ -│ │ -│ 1. Consultar @CATALOG_INDEX │ -│ → ¿Existe la funcionalidad? │ -│ │ -│ 2. Si existe: │ -│ → Leer {funcionalidad}/README.md │ -│ → Verificar compatibilidad de stack │ -│ → Seguir {funcionalidad}/IMPLEMENTATION.md │ -│ → Copiar código de _reference/ │ -│ │ -│ 3. Si NO existe: │ -│ → Implementar siguiendo @SIMCO │ -│ → Considerar agregar al catálogo después │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### Alias Disponibles - -```yaml -@CATALOG: core/catalog/ -@CATALOG_INDEX: core/catalog/CATALOG-INDEX.yml -@CATALOG_AUTH: core/catalog/auth/ -@CATALOG_SESSION: core/catalog/session-management/ -@CATALOG_RATELIMIT: core/catalog/rate-limiting/ -@CATALOG_NOTIFY: core/catalog/notifications/ -@CATALOG_TENANT: core/catalog/multi-tenancy/ -@CATALOG_FLAGS: core/catalog/feature-flags/ -@CATALOG_WS: core/catalog/websocket/ -@CATALOG_PAYMENTS: core/catalog/payments/ -``` - ---- - -## FUNCIONALIDADES DISPONIBLES - -### Estado Actual - -| Funcionalidad | Estado | Origen | Stack | -|---------------|--------|--------|-------| -| auth | 🟢 Production-Ready | Gamilit | NestJS + JWT + Passport | -| session-management | 🟢 Production-Ready | Gamilit | NestJS + TypeORM | -| rate-limiting | 🟢 Production-Ready | Gamilit | NestJS + @nestjs/throttler | -| notifications | 🟢 Production-Ready | Gamilit | NestJS + Email/Push/WebPush | -| multi-tenancy | 🟢 Production-Ready | Gamilit | NestJS + PostgreSQL RLS | -| feature-flags | 🟢 Production-Ready | Gamilit | NestJS + TypeORM | -| websocket | 🟢 Production-Ready | Trading | NestJS + Socket.io | -| payments | 🟢 Production-Ready | Trading | NestJS + Stripe | - -**Leyenda:** -- 🟢 Production-Ready: README.md + IMPLEMENTATION.md completos -- 🟡 Documentando: En proceso de documentación -- 🔴 Pendiente: Identificado pero no documentado - -**Última actualización:** 2025-12-08 - ---- - -## ESTRUCTURA DE CADA FUNCIONALIDAD - -Cada funcionalidad en el catálogo incluye: - -### README.md -```markdown -# {Nombre} - Catálogo de Funcionalidad - -## Metadata -- Versión, origen, estado, stack - -## Descripción -- Qué hace -- Cuándo usar -- Cuándo NO usar - -## Características -- Lista de features incluidas - -## Dependencias -- npm packages -- Tablas de BD -- Servicios externos - -## Trade-offs -- Limitaciones conocidas -- Alternativas -``` - -### IMPLEMENTATION.md -```markdown -# Implementación: {Nombre} - -## Prerequisitos -- Lo que debe existir antes - -## Pasos de Implementación -1. Paso detallado 1 -2. Paso detallado 2 -... - -## Configuración -- Variables de entorno -- Opciones de configuración - -## Verificación -- Cómo validar que funciona - -## Troubleshooting -- Problemas comunes y soluciones -``` - -### _reference/ -``` -_reference/ -├── {archivo1}.ts # Código de referencia -├── {archivo2}.ts -├── {archivo}.spec.ts # Tests -└── README.md # Descripción de cada archivo -``` - ---- - -## CONTRIBUIR AL CATÁLOGO - -### IMPORTANTE: Directiva de Mantenimiento - -``` -╔══════════════════════════════════════════════════════════════════════════════╗ -║ CUANDO IMPLEMENTES UNA FUNCIONALIDAD REUTILIZABLE EN UN PROYECTO: ║ -║ ║ -║ 1. EVALÚA si es candidata para el catálogo (ver criterios abajo) ║ -║ 2. DOCUMENTA mientras desarrollas (más fácil que después) ║ -║ 3. EXTRAE al catálogo cuando esté estable ║ -║ ║ -║ El catálogo crece con cada proyecto exitoso. ║ -╚══════════════════════════════════════════════════════════════════════════════╝ -``` - -### Criterios de Inclusión - -Una funcionalidad DEBE agregarse al catálogo si cumple **TODOS** estos criterios: - -| Criterio | Descripción | -|----------|-------------| -| ✅ Probada | Funciona en producción (al menos 1 proyecto) | -| ✅ Reutilizable | No es específica de un dominio de negocio | -| ✅ Común | Se necesita en múltiples proyectos típicamente | -| ✅ Compleja | Tiene suficiente complejidad que justifica documentar | -| ✅ Estable | API/estructura no cambia frecuentemente | - -**Ejemplos de funcionalidades candidatas:** -- Autenticación, sesiones, rate limiting -- Notificaciones, emails, push -- Pagos, suscripciones -- WebSockets, real-time -- Multi-tenancy, feature flags -- File upload, image processing -- Audit logs, activity tracking -- Caching strategies - -**Ejemplos que NO van al catálogo:** -- Lógica de negocio específica (cálculo de comisiones de trading) -- UI components específicos (aunque pueden ir a un catálogo de UI) -- Configuraciones específicas de un proyecto - -### Proceso de Adición (Checklist) - -```markdown -PASO 1: PREPARAR ESTRUCTURA -[ ] Crear directorio: core/catalog/{nombre-en-kebab-case}/ -[ ] Crear README.md vacío -[ ] Crear IMPLEMENTATION.md vacío - -PASO 2: DOCUMENTAR README.md -[ ] Agregar metadata (versión, origen, estado, fecha) -[ ] Escribir descripción clara -[ ] Listar características -[ ] Definir stack tecnológico -[ ] Listar dependencias NPM -[ ] Listar tablas de BD requeridas -[ ] Documentar endpoints principales -[ ] Agregar diagrama de flujo si aplica - -PASO 3: DOCUMENTAR IMPLEMENTATION.md -[ ] Listar pre-requisitos -[ ] Escribir pasos numerados de implementación -[ ] Incluir código de ejemplo en cada paso -[ ] Documentar variables de entorno -[ ] Crear checklist de verificación -[ ] Agregar sección de troubleshooting - -PASO 4: ACTUALIZAR ÍNDICES (OBLIGATORIO) -[ ] Actualizar CATALOG-INDEX.yml: - - Agregar entrada bajo funcionalidades: - - Incluir: nombre, path, alias, estado, origen, version - - Incluir: stack, keywords, caracteristicas - - Incluir: dependencias_npm, tablas_requeridas -[ ] Actualizar este README.md: - - Agregar fila en tabla de "Estado Actual" - - Agregar en estructura de directorios - -PASO 5: ACTUALIZAR ALIASES (OBLIGATORIO) -[ ] Editar core/orchestration/referencias/ALIASES.yml: - - Agregar alias @CATALOG_{NOMBRE} - - Ejemplo: @CATALOG_AUDIT: "core/catalog/audit-logs/" - -PASO 6: VALIDAR -[ ] Verificar que grep encuentra la funcionalidad en CATALOG-INDEX.yml -[ ] Verificar que el alias funciona -[ ] Revisar que README e IMPLEMENTATION son claros -``` - -### Plantilla para Nueva Funcionalidad - -**README.md:** -```markdown -# {Nombre de la Funcionalidad} - -**Versión:** 1.0.0 -**Origen:** projects/{proyecto} -**Estado:** Production-Ready | Documentando -**Última actualización:** YYYY-MM-DD - ---- - -## Descripción - -{Descripción clara de qué hace y para qué sirve} - ---- - -## Características - -| Característica | Descripción | -|----------------|-------------| -| ... | ... | - ---- - -## Stack Tecnológico - -\`\`\`yaml -backend: - framework: NestJS - # ... -\`\`\` - ---- - -## Dependencias NPM - -\`\`\`json -{ - "paquete": "^version" -} -\`\`\` - ---- - -## Tablas Requeridas - -| Tabla | Propósito | -|-------|-----------| -| ... | ... | - ---- - -## Endpoints Principales - -| Método | Ruta | Descripción | -|--------|------|-------------| -| ... | ... | ... | - ---- - -**Mantenido por:** Sistema NEXUS -**Proyecto origen:** {Proyecto} -``` - -**IMPLEMENTATION.md:** -```markdown -# Guía de Implementación: {Nombre} - -**Versión:** 1.0.0 -**Tiempo estimado:** X-Y horas -**Complejidad:** Baja | Media | Alta - ---- - -## Pre-requisitos - -- [ ] Requisito 1 -- [ ] Requisito 2 - ---- - -## Paso 1: {Nombre del Paso} - -{Descripción} - -\`\`\`typescript -// Código de ejemplo -\`\`\` - ---- - -## Variables de Entorno - -\`\`\`env -VARIABLE=valor -\`\`\` - ---- - -## Checklist de Implementación - -- [ ] Paso completado 1 -- [ ] Paso completado 2 -- [ ] Build pasa sin errores -- [ ] Tests pasan - ---- - -## Troubleshooting - -### Error: "{mensaje}" -{Solución} - ---- - -**Versión:** 1.0.0 -**Sistema:** SIMCO Catálogo -``` - ---- - -## INTEGRACIÓN CON SIMCO - -Este catálogo se integra con el sistema SIMCO: - -| Directiva | Integración | -|-----------|-------------| -| @SIMCO-REUTILIZAR | Directiva específica para reutilización | -| PRINCIPIO-ANTI-DUPLICACION | Verificar @CATALOG antes de crear | -| SIMCO-CREAR | Paso 0: Verificar catálogo | -| SIMCO-BUSCAR | Incluir @CATALOG como fuente | -| SIMCO-DELEGACION | Incluir @CATALOG en contexto | - ---- - -## MANTENIMIENTO - -### Actualizar Funcionalidad Existente - -```markdown -Cuando el código de referencia mejore en un proyecto: - -1. Evaluar si el cambio es generalizable -2. Si sí: actualizar _reference/ y IMPLEMENTATION.md -3. Incrementar versión en README.md -4. Documentar cambio en historial -``` - -### Deprecar Funcionalidad - -```markdown -Si una funcionalidad ya no es recomendada: - -1. Marcar como "⚠️ Deprecated" en README.md -2. Documentar razón y alternativa -3. NO eliminar inmediatamente -4. Eliminar después de 2 versiones mayores -``` - ---- - -## REFERENCIAS - -- **Sistema SIMCO:** `/core/orchestration/directivas/simco/` -- **Principios:** `/core/orchestration/directivas/principios/` -- **Aliases:** `/core/orchestration/referencias/ALIASES.yml` - ---- - -**Versión:** 1.0.0 | **Sistema:** NEXUS SIMCO | **Mantenido por:** Tech Lead diff --git a/shared/libs/auth/IMPLEMENTATION.md b/shared/libs/auth/IMPLEMENTATION.md deleted file mode 100644 index 0043f4c4a..000000000 --- a/shared/libs/auth/IMPLEMENTATION.md +++ /dev/null @@ -1,733 +0,0 @@ -# Guía de Implementación: Autenticación - -**Versión:** 1.0.0 -**Tiempo estimado:** 2-4 horas (adaptación), 8-16 horas (desde cero) -**Complejidad:** Media-Alta - ---- - -## Pre-requisitos - -Antes de implementar, asegurar: - -- [ ] NestJS configurado con TypeORM -- [ ] PostgreSQL con schemas creados -- [ ] Variables de entorno configuradas -- [ ] Dependencias npm instaladas - ---- - -## Paso 1: Instalar Dependencias - -```bash -npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt -npm install -D @types/passport-jwt @types/bcrypt -npm install class-validator class-transformer -``` - ---- - -## Paso 2: Crear DDL de Base de Datos - -### 2.1 Schema auth.users - -```sql --- Schema: auth (si no existe) -CREATE SCHEMA IF NOT EXISTS auth; - --- Extensión para UUIDs -CREATE EXTENSION IF NOT EXISTS "pgcrypto"; - --- Tabla de usuarios -CREATE TABLE auth.users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - email TEXT NOT NULL UNIQUE, - encrypted_password TEXT NOT NULL, - role VARCHAR(50) DEFAULT 'user', - status VARCHAR(50) DEFAULT 'active', - email_confirmed_at TIMESTAMPTZ, - phone TEXT, - phone_confirmed_at TIMESTAMPTZ, - is_super_admin BOOLEAN DEFAULT FALSE, - banned_until TIMESTAMPTZ, - last_sign_in_at TIMESTAMPTZ, - raw_user_meta_data JSONB DEFAULT '{}', - deleted_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Índices -CREATE INDEX idx_auth_users_email ON auth.users(email); -CREATE INDEX idx_auth_users_role ON auth.users(role); -CREATE INDEX idx_auth_users_status ON auth.users(status); - --- Comentarios -COMMENT ON TABLE auth.users IS 'Tabla principal de usuarios del sistema'; -COMMENT ON COLUMN auth.users.encrypted_password IS 'Password hasheado con bcrypt'; -``` - -### 2.2 Schema auth_management.user_sessions - -```sql --- Schema: auth_management -CREATE SCHEMA IF NOT EXISTS auth_management; - --- Tabla de sesiones -CREATE TABLE auth_management.user_sessions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - tenant_id UUID, - session_token TEXT NOT NULL UNIQUE, - refresh_token TEXT NOT NULL, - ip_address INET, - user_agent TEXT, - device_type VARCHAR(50), - browser VARCHAR(100), - os VARCHAR(100), - is_active BOOLEAN DEFAULT TRUE, - expires_at TIMESTAMPTZ NOT NULL, - last_activity_at TIMESTAMPTZ DEFAULT NOW(), - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Índices -CREATE INDEX idx_user_sessions_user_id ON auth_management.user_sessions(user_id); -CREATE INDEX idx_user_sessions_refresh_token ON auth_management.user_sessions(refresh_token); -CREATE INDEX idx_user_sessions_expires_at ON auth_management.user_sessions(expires_at); - -COMMENT ON TABLE auth_management.user_sessions IS 'Sesiones activas de usuarios'; -``` - -### 2.3 Tabla auth_attempts - -```sql -CREATE TABLE auth_management.auth_attempts ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - email TEXT NOT NULL, - success BOOLEAN NOT NULL, - ip_address INET NOT NULL, - user_agent TEXT, - failure_reason TEXT, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_auth_attempts_email ON auth_management.auth_attempts(email); -CREATE INDEX idx_auth_attempts_ip ON auth_management.auth_attempts(ip_address); -CREATE INDEX idx_auth_attempts_created_at ON auth_management.auth_attempts(created_at); - -COMMENT ON TABLE auth_management.auth_attempts IS 'Log de intentos de autenticación'; -``` - ---- - -## Paso 3: Crear Entities - -### 3.1 User Entity - -```typescript -// src/modules/auth/entities/user.entity.ts -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - Index, -} from 'typeorm'; -import { Exclude } from 'class-transformer'; - -@Entity({ schema: 'auth', name: 'users' }) -@Index('idx_auth_users_email', ['email']) -export class User { - @PrimaryGeneratedColumn('uuid') - id!: string; - - @Column({ type: 'text', unique: true }) - email!: string; - - @Column({ type: 'text', name: 'encrypted_password' }) - @Exclude() // No serializar en respuestas - encrypted_password!: string; - - @Column({ type: 'varchar', length: 50, default: 'user' }) - role!: string; - - @Column({ type: 'varchar', length: 50, default: 'active' }) - status!: string; - - @Column({ type: 'timestamp with time zone', nullable: true }) - email_confirmed_at?: Date; - - @Column({ type: 'boolean', default: false }) - is_super_admin!: boolean; - - @Column({ type: 'timestamp with time zone', nullable: true }) - banned_until?: Date; - - @Column({ type: 'timestamp with time zone', nullable: true }) - last_sign_in_at?: Date; - - @Column({ type: 'jsonb', default: {} }) - raw_user_meta_data!: Record; - - @Column({ type: 'timestamp with time zone', nullable: true }) - deleted_at?: Date; - - @CreateDateColumn({ type: 'timestamp with time zone' }) - created_at!: Date; - - @UpdateDateColumn({ type: 'timestamp with time zone' }) - updated_at!: Date; -} -``` - -### 3.2 UserSession Entity - -```typescript -// src/modules/auth/entities/user-session.entity.ts -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - ManyToOne, - JoinColumn, -} from 'typeorm'; -import { User } from './user.entity'; - -@Entity({ schema: 'auth_management', name: 'user_sessions' }) -export class UserSession { - @PrimaryGeneratedColumn('uuid') - id!: string; - - @Column({ type: 'uuid' }) - user_id!: string; - - @Column({ type: 'uuid', nullable: true }) - tenant_id?: string; - - @Column({ type: 'text', unique: true }) - session_token!: string; - - @Column({ type: 'text' }) - refresh_token!: string; // Hasheado con SHA256 - - @Column({ type: 'inet', nullable: true }) - ip_address?: string; - - @Column({ type: 'text', nullable: true }) - user_agent?: string; - - @Column({ type: 'varchar', length: 50, nullable: true }) - device_type?: string; - - @Column({ type: 'varchar', length: 100, nullable: true }) - browser?: string; - - @Column({ type: 'varchar', length: 100, nullable: true }) - os?: string; - - @Column({ type: 'boolean', default: true }) - is_active!: boolean; - - @Column({ type: 'timestamp with time zone' }) - expires_at!: Date; - - @Column({ type: 'timestamp with time zone', default: () => 'NOW()' }) - last_activity_at!: Date; - - @CreateDateColumn({ type: 'timestamp with time zone' }) - created_at!: Date; - - @UpdateDateColumn({ type: 'timestamp with time zone' }) - updated_at!: Date; -} -``` - ---- - -## Paso 4: Crear DTOs - -### 4.1 Login DTO - -```typescript -// src/modules/auth/dto/login.dto.ts -import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator'; - -export class LoginDto { - @ApiProperty({ - description: 'Email del usuario', - example: 'usuario@example.com', - }) - @IsEmail({}, { message: 'Email inválido' }) - @IsNotEmpty({ message: 'Email es requerido' }) - email!: string; - - @ApiProperty({ - description: 'Contraseña del usuario', - example: 'MySecurePassword123!', - minLength: 8, - }) - @IsString() - @MinLength(8, { message: 'Password debe tener al menos 8 caracteres' }) - @IsNotEmpty({ message: 'Password es requerido' }) - password!: string; -} -``` - -### 4.2 Register DTO - -```typescript -// src/modules/auth/dto/register-user.dto.ts -import { IsEmail, IsString, MinLength, IsOptional, IsObject } from 'class-validator'; - -export class RegisterUserDto { - @IsEmail({}, { message: 'El email debe ser válido' }) - email!: string; - - @IsString() - @MinLength(8, { message: 'La contraseña debe tener al menos 8 caracteres' }) - password!: string; - - @IsObject() - @IsOptional() - raw_user_meta_data?: Record; - - @IsString() - @IsOptional() - first_name?: string; - - @IsString() - @IsOptional() - last_name?: string; -} -``` - ---- - -## Paso 5: Crear JWT Strategy - -```typescript -// src/modules/auth/strategies/jwt.strategy.ts -import { Injectable, UnauthorizedException } from '@nestjs/common'; -import { PassportStrategy } from '@nestjs/passport'; -import { ExtractJwt, Strategy } from 'passport-jwt'; -import { ConfigService } from '@nestjs/config'; -import { AuthService } from '../services/auth.service'; - -@Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { - constructor( - private readonly configService: ConfigService, - private readonly authService: AuthService, - ) { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - ignoreExpiration: false, - secretOrKey: configService.get('JWT_SECRET') || 'dev-secret', - }); - } - - async validate(payload: any) { - const { sub: userId } = payload; - const user = await this.authService.validateUser(userId); - - if (!user) { - throw new UnauthorizedException('Usuario no encontrado o inactivo'); - } - - return { - id: user.id, - sub: user.id, - email: user.email, - role: user.role, - is_active: !user.deleted_at, - email_verified: !!user.email_confirmed_at, - }; - } -} -``` - ---- - -## Paso 6: Crear Guards - -### 6.1 JWT Auth Guard - -```typescript -// src/modules/auth/guards/jwt-auth.guard.ts -import { Injectable, ExecutionContext } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; - -@Injectable() -export class JwtAuthGuard extends AuthGuard('jwt') { - canActivate(context: ExecutionContext) { - return super.canActivate(context); - } -} -``` - -### 6.2 Roles Guard - -```typescript -// src/modules/auth/guards/roles.guard.ts -import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; - -@Injectable() -export class RolesGuard implements CanActivate { - constructor(private reflector: Reflector) {} - - canActivate(context: ExecutionContext): boolean { - const requiredRoles = this.reflector.getAllAndOverride('roles', [ - context.getHandler(), - context.getClass(), - ]); - - if (!requiredRoles || requiredRoles.length === 0) { - return true; - } - - const { user } = context.switchToHttp().getRequest(); - - if (!user) { - return false; - } - - return requiredRoles.some((role) => user.role === role); - } -} -``` - -### 6.3 Roles Decorator - -```typescript -// src/modules/auth/decorators/roles.decorator.ts -import { SetMetadata } from '@nestjs/common'; - -export const Roles = (...roles: string[]) => SetMetadata('roles', roles); -``` - ---- - -## Paso 7: Crear AuthService - -```typescript -// src/modules/auth/services/auth.service.ts -import { Injectable, UnauthorizedException, ConflictException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { JwtService } from '@nestjs/jwt'; -import * as bcrypt from 'bcrypt'; -import * as crypto from 'crypto'; -import { User } from '../entities/user.entity'; -import { UserSession } from '../entities/user-session.entity'; -import { LoginDto, RegisterUserDto } from '../dto'; - -@Injectable() -export class AuthService { - constructor( - @InjectRepository(User) - private readonly userRepository: Repository, - - @InjectRepository(UserSession) - private readonly sessionRepository: Repository, - - private readonly jwtService: JwtService, - ) {} - - async register(dto: RegisterUserDto, ip?: string, userAgent?: string) { - // Verificar email único - const existing = await this.userRepository.findOne({ - where: { email: dto.email }, - }); - - if (existing) { - throw new ConflictException('Email ya registrado'); - } - - // Hashear password - const hashedPassword = await bcrypt.hash(dto.password, 10); - - // Crear usuario - const user = this.userRepository.create({ - email: dto.email, - encrypted_password: hashedPassword, - role: 'user', - raw_user_meta_data: dto.raw_user_meta_data || {}, - }); - await this.userRepository.save(user); - - // Generar tokens - const tokens = await this.generateTokens(user); - - // Crear sesión - await this.createSession(user.id, tokens.refreshToken, ip, userAgent); - - return { - user: this.toUserResponse(user), - ...tokens, - }; - } - - async login(dto: LoginDto, ip?: string, userAgent?: string) { - const user = await this.userRepository.findOne({ - where: { email: dto.email }, - }); - - if (!user) { - throw new UnauthorizedException('Credenciales inválidas'); - } - - const isPasswordValid = await bcrypt.compare(dto.password, user.encrypted_password); - - if (!isPasswordValid) { - throw new UnauthorizedException('Credenciales inválidas'); - } - - if (user.deleted_at) { - throw new UnauthorizedException('Usuario no activo'); - } - - // Generar tokens - const tokens = await this.generateTokens(user); - - // Crear sesión - await this.createSession(user.id, tokens.refreshToken, ip, userAgent); - - // Actualizar último login - user.last_sign_in_at = new Date(); - await this.userRepository.save(user); - - return { - user: this.toUserResponse(user), - ...tokens, - }; - } - - async validateUser(userId: string): Promise { - const user = await this.userRepository.findOne({ - where: { id: userId }, - }); - - if (user && user.deleted_at) { - return null; - } - - return user; - } - - async refreshToken(refreshToken: string) { - try { - const payload = this.jwtService.verify(refreshToken); - const user = await this.validateUser(payload.sub); - - if (!user) { - throw new UnauthorizedException('Usuario no encontrado'); - } - - // Verificar sesión - const hashedToken = crypto.createHash('sha256').update(refreshToken).digest('hex'); - const session = await this.sessionRepository.findOne({ - where: { user_id: user.id, refresh_token: hashedToken }, - }); - - if (!session || new Date() > session.expires_at) { - throw new UnauthorizedException('Sesión expirada'); - } - - // Generar nuevos tokens - const tokens = await this.generateTokens(user); - - // Actualizar sesión - session.refresh_token = crypto.createHash('sha256').update(tokens.refreshToken).digest('hex'); - session.expires_at = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); - session.last_activity_at = new Date(); - await this.sessionRepository.save(session); - - return tokens; - } catch { - throw new UnauthorizedException('Refresh token inválido'); - } - } - - private async generateTokens(user: User) { - const payload = { sub: user.id, email: user.email, role: user.role }; - - return { - accessToken: this.jwtService.sign(payload, { expiresIn: '15m' }), - refreshToken: this.jwtService.sign(payload, { expiresIn: '7d' }), - }; - } - - private async createSession(userId: string, refreshToken: string, ip?: string, userAgent?: string) { - const hashedRefreshToken = crypto.createHash('sha256').update(refreshToken).digest('hex'); - const sessionToken = crypto.randomBytes(32).toString('hex'); - const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); - - const session = this.sessionRepository.create({ - user_id: userId, - session_token: sessionToken, - refresh_token: hashedRefreshToken, - ip_address: ip || null, - user_agent: userAgent || null, - expires_at: expiresAt, - is_active: true, - }); - - return this.sessionRepository.save(session); - } - - private toUserResponse(user: User) { - const { encrypted_password, ...userWithoutPassword } = user; - return { - ...userWithoutPassword, - emailVerified: !!user.email_confirmed_at, - isActive: !user.deleted_at, - }; - } -} -``` - ---- - -## Paso 8: Crear AuthModule - -```typescript -// src/modules/auth/auth.module.ts -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { JwtModule } from '@nestjs/jwt'; -import { PassportModule } from '@nestjs/passport'; -import { ConfigModule, ConfigService } from '@nestjs/config'; - -import { User } from './entities/user.entity'; -import { UserSession } from './entities/user-session.entity'; -import { AuthService } from './services/auth.service'; -import { AuthController } from './controllers/auth.controller'; -import { JwtStrategy } from './strategies/jwt.strategy'; - -@Module({ - imports: [ - PassportModule.register({ defaultStrategy: 'jwt' }), - - JwtModule.registerAsync({ - imports: [ConfigModule], - useFactory: async (configService: ConfigService) => ({ - secret: configService.get('JWT_SECRET') || 'dev-secret', - signOptions: { - expiresIn: configService.get('JWT_EXPIRES_IN') || '15m', - }, - }), - inject: [ConfigService], - }), - - TypeOrmModule.forFeature([User, UserSession]), - ], - controllers: [AuthController], - providers: [AuthService, JwtStrategy], - exports: [AuthService, JwtModule, PassportModule], -}) -export class AuthModule {} -``` - ---- - -## Paso 9: Crear AuthController - -```typescript -// src/modules/auth/controllers/auth.controller.ts -import { Controller, Post, Body, Req, UseGuards, Get, HttpCode, HttpStatus } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; -import { Request } from 'express'; -import { AuthService } from '../services/auth.service'; -import { LoginDto, RegisterUserDto, RefreshTokenDto } from '../dto'; -import { JwtAuthGuard } from '../guards/jwt-auth.guard'; - -@ApiTags('Auth') -@Controller('auth') -export class AuthController { - constructor(private readonly authService: AuthService) {} - - @Post('register') - @ApiOperation({ summary: 'Registrar nuevo usuario' }) - async register(@Body() dto: RegisterUserDto, @Req() req: Request) { - const ip = req.ip; - const userAgent = req.headers['user-agent']; - return this.authService.register(dto, ip, userAgent); - } - - @Post('login') - @HttpCode(HttpStatus.OK) - @ApiOperation({ summary: 'Login con email y password' }) - async login(@Body() dto: LoginDto, @Req() req: Request) { - const ip = req.ip; - const userAgent = req.headers['user-agent']; - return this.authService.login(dto, ip, userAgent); - } - - @Post('refresh') - @HttpCode(HttpStatus.OK) - @ApiOperation({ summary: 'Renovar access token' }) - async refresh(@Body() dto: RefreshTokenDto) { - return this.authService.refreshToken(dto.refreshToken); - } - - @Get('me') - @UseGuards(JwtAuthGuard) - @ApiBearerAuth() - @ApiOperation({ summary: 'Obtener usuario actual' }) - async me(@Req() req: Request) { - return req.user; - } -} -``` - ---- - -## Checklist de Implementación - -- [ ] Dependencias npm instaladas -- [ ] DDL de base de datos creado -- [ ] Entities creados y alineados con DDL -- [ ] DTOs con validaciones -- [ ] JWT Strategy configurado -- [ ] Guards (JWT y Roles) creados -- [ ] AuthService con login/register/refresh -- [ ] AuthModule exporta servicios necesarios -- [ ] AuthController con endpoints -- [ ] Variables de entorno configuradas -- [ ] Build pasa sin errores -- [ ] Tests básicos funcionando - ---- - -## Troubleshooting - -### Error: "Cannot find module 'bcrypt'" -```bash -npm install bcrypt -npm install -D @types/bcrypt -``` - -### Error: "JWT secret not configured" -Verificar variable de entorno `JWT_SECRET` en `.env` - -### Error: "relation auth.users does not exist" -Ejecutar DDL para crear tablas antes de iniciar la aplicación - ---- - -## Código de Referencia - -Ver implementación completa en: -- `projects/gamilit/apps/backend/src/modules/auth/` - ---- - -**Versión:** 1.0.0 -**Sistema:** SIMCO Catálogo diff --git a/shared/libs/auth/README.md b/shared/libs/auth/README.md deleted file mode 100644 index de0529a28..000000000 --- a/shared/libs/auth/README.md +++ /dev/null @@ -1,283 +0,0 @@ -# Autenticación y Autorización - -**Versión:** 1.0.0 -**Origen:** projects/gamilit -**Estado:** Producción -**Última actualización:** 2025-12-08 - ---- - -## Descripción - -Sistema completo de autenticación y autorización basado en JWT con: -- Registro y login de usuarios -- Tokens de acceso y refresh -- Gestión de sesiones -- Guards y decoradores -- Sistema de roles (RBAC) -- Recuperación de contraseña -- Verificación de email - ---- - -## Características - -| Característica | Descripción | -|----------------|-------------| -| JWT Access Tokens | Tokens de corta duración (15min) para autenticación | -| JWT Refresh Tokens | Tokens de larga duración (7d) para renovación | -| Password Hashing | bcrypt con cost 10 | -| Session Management | Sesiones persistentes en BD con metadata | -| Role-Based Access | Sistema RBAC con guards y decoradores | -| Multi-tenant | Soporte para múltiples tenants | -| Security Logging | Registro de intentos de autenticación | - ---- - -## Stack Tecnológico - -```yaml -backend: - framework: NestJS - orm: TypeORM - auth_library: "@nestjs/passport" - jwt_library: "@nestjs/jwt" - hashing: bcrypt - validation: class-validator - -database: - engine: PostgreSQL - schemas: - - auth (usuarios principales) - - auth_management (perfiles, sesiones, tokens) -``` - ---- - -## Dependencias NPM - -```json -{ - "@nestjs/jwt": "^10.x", - "@nestjs/passport": "^10.x", - "passport": "^0.7.x", - "passport-jwt": "^4.x", - "bcrypt": "^5.x", - "class-validator": "^0.14.x", - "class-transformer": "^0.5.x" -} -``` - ---- - -## Tablas Requeridas - -| Schema | Tabla | Propósito | -|--------|-------|-----------| -| auth | users | Usuarios del sistema | -| auth_management | profiles | Perfiles extendidos | -| auth_management | tenants | Multi-tenancy | -| auth_management | roles | Definición de roles | -| auth_management | user_roles | Asignación de roles | -| auth_management | user_sessions | Sesiones activas | -| auth_management | auth_attempts | Log de intentos | -| auth_management | password_reset_tokens | Tokens de reset | -| auth_management | email_verification_tokens | Tokens de verificación | - ---- - -## Estructura del Módulo - -``` -auth/ -├── auth.module.ts # Módulo principal -├── controllers/ -│ ├── auth.controller.ts # Login, register, refresh -│ ├── password.controller.ts # Reset, change password -│ └── users.controller.ts # Profile, preferences -├── services/ -│ ├── auth.service.ts # Lógica de autenticación -│ ├── session-management.service.ts -│ ├── security.service.ts -│ ├── password-recovery.service.ts -│ └── email-verification.service.ts -├── entities/ -│ ├── user.entity.ts -│ ├── profile.entity.ts -│ ├── tenant.entity.ts -│ ├── role.entity.ts -│ ├── user-role.entity.ts -│ ├── user-session.entity.ts -│ ├── auth-attempt.entity.ts -│ └── ... -├── dto/ -│ ├── login.dto.ts -│ ├── register-user.dto.ts -│ ├── refresh-token.dto.ts -│ ├── change-password.dto.ts -│ └── ... -├── guards/ -│ ├── jwt-auth.guard.ts -│ └── roles.guard.ts -├── strategies/ -│ └── jwt.strategy.ts -├── decorators/ -│ └── roles.decorator.ts -└── __tests__/ - ├── auth.controller.spec.ts - ├── auth.service.spec.ts - └── ... -``` - ---- - -## Endpoints Principales - -| Método | Ruta | Descripción | Auth | -|--------|------|-------------|------| -| POST | /auth/register | Registro público | No | -| POST | /auth/login | Login con email/password | No | -| POST | /auth/refresh | Renovar access token | Refresh Token | -| POST | /auth/logout | Cerrar sesión | JWT | -| GET | /auth/me | Obtener usuario actual | JWT | -| POST | /auth/change-password | Cambiar contraseña | JWT | -| POST | /auth/request-reset | Solicitar reset password | No | -| POST | /auth/reset-password | Reset con token | Token | - ---- - -## Uso Rápido - -### 1. Proteger una ruta con JWT - -```typescript -import { Controller, Get, UseGuards, Request } from '@nestjs/common'; -import { JwtAuthGuard } from '@/modules/auth/guards'; - -@Controller('protected') -export class ProtectedController { - @Get() - @UseGuards(JwtAuthGuard) - getProtectedData(@Request() req) { - // req.user contiene el payload del JWT - return { userId: req.user.id, email: req.user.email }; - } -} -``` - -### 2. Proteger por roles - -```typescript -import { Controller, Get, UseGuards } from '@nestjs/common'; -import { JwtAuthGuard, RolesGuard } from '@/modules/auth/guards'; -import { Roles } from '@/modules/auth/decorators'; -import { RoleEnum } from '@/shared/constants'; - -@Controller('admin') -@UseGuards(JwtAuthGuard, RolesGuard) -export class AdminController { - @Get() - @Roles(RoleEnum.ADMIN, RoleEnum.SUPER_ADMIN) - adminOnly() { - return { message: 'Solo para administradores' }; - } -} -``` - -### 3. Configurar el módulo - -```typescript -// app.module.ts -import { AuthModule } from '@/modules/auth/auth.module'; - -@Module({ - imports: [ - AuthModule, - // ... otros módulos - ], -}) -export class AppModule {} -``` - ---- - -## Variables de Entorno Requeridas - -```env -# JWT -JWT_SECRET=your-super-secret-key-change-in-production -JWT_EXPIRES_IN=15m -JWT_REFRESH_EXPIRES_IN=7d - -# Base de datos (para TypeORM) -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=your_database -DB_USER=your_user -DB_PASSWORD=your_password -``` - ---- - -## Flujos de Autenticación - -### Login Flow - -``` -1. Cliente envía email + password -2. AuthService valida credenciales -3. Si válidas: - - Genera access token (15min) - - Genera refresh token (7d) - - Crea sesión en BD - - Registra intento exitoso -4. Retorna tokens + user data -``` - -### Refresh Flow - -``` -1. Cliente envía refresh token -2. AuthService verifica token -3. Busca sesión en BD por hash del refresh token -4. Si válida y no expirada: - - Genera nuevos tokens - - Actualiza sesión -5. Retorna nuevos tokens -``` - ---- - -## Seguridad - -- Passwords hasheados con bcrypt (cost 10) -- Refresh tokens hasheados en BD (SHA256) -- Tokens JWT con expiración corta -- Sesiones con metadata (IP, User-Agent, dispositivo) -- Logging de todos los intentos de auth -- Soft delete para usuarios (no eliminación física) - ---- - -## Adaptaciones Necesarias - -Al implementar en un nuevo proyecto, ajustar: - -1. **Enum de roles**: Definir roles específicos del proyecto -2. **Schema de BD**: Puede ser diferente a `auth`/`auth_management` -3. **Campos de User entity**: Agregar/quitar según necesidades -4. **Conexión TypeORM**: Ajustar nombre de conexión si no es 'auth' -5. **Variables de entorno**: Configurar JWT_SECRET único - ---- - -## Referencias - -- [NestJS Authentication](https://docs.nestjs.com/security/authentication) -- [Passport.js](http://www.passportjs.org/) -- [JWT.io](https://jwt.io/) - ---- - -**Mantenido por:** Sistema NEXUS -**Proyecto origen:** Gamilit Platform diff --git a/shared/libs/auth/_reference/README.md b/shared/libs/auth/_reference/README.md deleted file mode 100644 index 4dbacc57c..000000000 --- a/shared/libs/auth/_reference/README.md +++ /dev/null @@ -1,243 +0,0 @@ -# AUTH - REFERENCE IMPLEMENTATION - -**Versión:** 1.0.0 | **Fecha:** 2025-12-12 | **Nivel:** Catalog (3) - ---- - -## ÍNDICE DE ARCHIVOS - -| Archivo | Descripción | LOC | Patrón Principal | -|---------|-------------|-----|------------------| -| `auth.service.reference.ts` | Servicio completo de autenticación JWT | 230 | Register, Login, Refresh, Logout | -| `jwt-auth.guard.reference.ts` | Guard para proteger rutas con JWT | ~60 | Passport JWT Guard | -| `jwt.strategy.reference.ts` | Estrategia Passport para validación JWT | ~70 | Passport Strategy | -| `roles.guard.reference.ts` | Guard para control de acceso basado en roles | ~65 | RBAC (Role-Based Access Control) | - ---- - -## CÓMO USAR - -### Flujo de adopción recomendado - -```yaml -PASO_1: Identificar necesidades - - ¿Necesitas autenticación completa? → auth.service.reference.ts - - ¿Solo proteger rutas? → jwt-auth.guard.reference.ts - - ¿Control por roles? → roles.guard.reference.ts + jwt.strategy.reference.ts - -PASO_2: Copiar archivos base - - Copiar archivos necesarios a tu módulo de auth - - Renombrar eliminando ".reference" - -PASO_3: Adaptar imports - - Ajustar rutas de entidades (User, Profile, UserSession) - - Ajustar DTOs según tu esquema - - Configurar conexión a BD correcta (@InjectRepository) - -PASO_4: Configurar variables de entorno - - JWT_SECRET - - JWT_EXPIRES_IN (default: 15m) - - REFRESH_TOKEN_EXPIRES (default: 7d) - - BCRYPT_COST (default: 10) - -PASO_5: Implementar entidades requeridas - - User: id, email, encrypted_password, role - - Profile: id, user_id, email, ... - - UserSession: id, user_id, refresh_token, expires_at, is_revoked - -PASO_6: Validar integración - - npm run build - - npm run lint - - Pruebas de endpoints: /auth/register, /auth/login, /auth/refresh -``` - ---- - -## PATRONES IMPLEMENTADOS - -### 1. Autenticación JWT (auth.service.reference.ts) - -**Características:** -- Registro con email único -- Password hasheado con bcrypt (cost 10) -- Access token (15m) + Refresh token (7d) -- Gestión de sesiones con revocación -- Refresh token hasheado (SHA-256) en BD - -**Endpoints típicos:** -```typescript -POST /auth/register → { accessToken, refreshToken, user } -POST /auth/login → { accessToken, refreshToken, user } -POST /auth/refresh → { accessToken, refreshToken, user } -POST /auth/logout → { message: 'Logout successful' } -``` - -### 2. Guards de protección - -**jwt-auth.guard.reference.ts:** -```typescript -// Uso en controladores -@UseGuards(JwtAuthGuard) -@Get('profile') -getProfile(@Request() req) { - return req.user; // Usuario del token JWT -} -``` - -**roles.guard.reference.ts:** -```typescript -// Control de acceso por roles -@UseGuards(JwtAuthGuard, RolesGuard) -@Roles('admin', 'moderator') -@Delete('users/:id') -deleteUser(@Param('id') id: string) { - // Solo admin y moderator pueden ejecutar -} -``` - -### 3. Estrategia JWT (jwt.strategy.reference.ts) - -**Funcionalidad:** -- Valida tokens en cada request -- Extrae payload del token -- Inyecta `req.user` con datos del usuario - ---- - -## NOTAS DE ADAPTACIÓN - -### Variables a reemplazar - -```typescript -// EN auth.service.reference.ts -User → Tu entidad de usuario -Profile → Tu entidad de perfil (opcional) -UserSession → Tu entidad de sesiones -RegisterUserDto → Tu DTO de registro - -// EN jwt.strategy.reference.ts -JWT_SECRET → process.env.JWT_SECRET -userRepository → Tu repositorio/servicio de usuarios - -// EN roles.guard.reference.ts -@Roles() → Tu decorador custom (crear si no existe) -``` - -### Dependencias requeridas - -```bash -npm install --save @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt -npm install --save-dev @types/passport-jwt @types/bcrypt -``` - -### Esquema de base de datos - -```sql --- Tabla users (adaptar según tu schema) -CREATE TABLE users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - email VARCHAR(255) UNIQUE NOT NULL, - encrypted_password VARCHAR(255) NOT NULL, - role VARCHAR(50) DEFAULT 'user', - created_at TIMESTAMP DEFAULT NOW() -); - --- Tabla user_sessions -CREATE TABLE user_sessions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES users(id) ON DELETE CASCADE, - refresh_token VARCHAR(64) NOT NULL, -- SHA-256 hash (64 chars) - expires_at TIMESTAMP NOT NULL, - is_revoked BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT NOW() -); - -CREATE INDEX idx_user_sessions_refresh_token ON user_sessions(refresh_token); -``` - ---- - -## CASOS DE USO COMUNES - -### 1. Integrar autenticación en proyecto nuevo - -```typescript -// 1. Copiar auth.service.reference.ts → auth.service.ts -// 2. Copiar guards y strategy -// 3. Configurar en auth.module.ts: - -@Module({ - imports: [ - TypeOrmModule.forFeature([User, Profile, UserSession], 'auth'), - JwtModule.register({ - secret: process.env.JWT_SECRET, - signOptions: { expiresIn: '15m' }, - }), - PassportModule, - ], - providers: [AuthService, JwtStrategy, RolesGuard], - controllers: [AuthController], - exports: [AuthService], -}) -export class AuthModule {} -``` - -### 2. Migrar de autenticación básica a JWT - -```typescript -// Antes: session-based -@UseGuards(SessionGuard) - -// Después: JWT-based -@UseGuards(JwtAuthGuard) -``` - -### 3. Agregar roles a usuarios existentes - -```typescript -// 1. Migrar BD: ALTER TABLE users ADD COLUMN role VARCHAR(50) DEFAULT 'user'; -// 2. Implementar RolesGuard (roles.guard.reference.ts) -// 3. Usar decorador @Roles() en rutas protegidas -``` - ---- - -## CHECKLIST DE VALIDACIÓN - -Antes de marcar como completo: - -- [ ] Build pasa: `npm run build` -- [ ] Lint pasa: `npm run lint` -- [ ] Registro funciona: POST /auth/register -- [ ] Login funciona: POST /auth/login -- [ ] Refresh funciona: POST /auth/refresh -- [ ] Guards protegen rutas correctamente -- [ ] Tokens expiran según configuración -- [ ] Logout revoca sesión en BD -- [ ] Roles bloquean acceso no autorizado - ---- - -## REFERENCIAS CRUZADAS - -### Dependencias en @CATALOG - -- **session-management**: Para gestión avanzada de sesiones multi-dispositivo -- **multi-tenancy**: Si necesitas autenticación por tenant -- **rate-limiting**: Proteger endpoints de auth contra brute-force - -### Relacionado con SIMCO - -- **@OP_BACKEND**: Operaciones de backend (crear service, guards) -- **@SIMCO-REUTILIZAR**: Este catálogo es candidato para reutilización -- **@SIMCO-VALIDAR**: Validar implementación antes de deploy - -### Documentación adicional - -- NestJS Auth: https://docs.nestjs.com/security/authentication -- Passport JWT: http://www.passportjs.org/packages/passport-jwt/ -- bcrypt: https://github.com/kelektiv/node.bcrypt.js - ---- - -**Mantenido por:** Core Team | **Origen:** gamilit/apps/backend/src/modules/auth/ diff --git a/shared/libs/auth/_reference/auth.service.reference.ts b/shared/libs/auth/_reference/auth.service.reference.ts deleted file mode 100644 index 0595cdedb..000000000 --- a/shared/libs/auth/_reference/auth.service.reference.ts +++ /dev/null @@ -1,229 +0,0 @@ -/** - * AUTH SERVICE - REFERENCE IMPLEMENTATION - * - * @description Implementación de referencia para servicio de autenticación JWT. - * Este archivo muestra los patrones básicos sin dependencias específicas de proyecto. - * - * @usage Copiar y adaptar según necesidades del proyecto. - * @origin gamilit/apps/backend/src/modules/auth/services/auth.service.ts - */ - -import { Injectable, UnauthorizedException, ConflictException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { JwtService } from '@nestjs/jwt'; -import * as bcrypt from 'bcrypt'; -import * as crypto from 'crypto'; - -// Adaptar imports según proyecto -// import { User, Profile, UserSession } from '../entities'; -// import { RegisterUserDto } from '../dto'; - -/** - * Constantes de configuración - * Mover a variables de entorno en producción - */ -const BCRYPT_COST = 10; -const ACCESS_TOKEN_EXPIRES = '15m'; -const REFRESH_TOKEN_EXPIRES = '7d'; - -@Injectable() -export class AuthService { - constructor( - @InjectRepository(User, 'auth') - private readonly userRepository: Repository, - - @InjectRepository(Profile, 'auth') - private readonly profileRepository: Repository, - - @InjectRepository(UserSession, 'auth') - private readonly sessionRepository: Repository, - - private readonly jwtService: JwtService, - ) {} - - /** - * Registro de nuevo usuario - * - * @pattern - * 1. Validar email único - * 2. Hashear password (bcrypt) - * 3. Crear usuario - * 4. Crear perfil - * 5. Generar tokens - */ - async register(dto: RegisterUserDto): Promise { - // 1. Validar email único - const existingUser = await this.userRepository.findOne({ - where: { email: dto.email }, - }); - if (existingUser) { - throw new ConflictException('Email ya registrado'); - } - - // 2. Hashear password - const hashedPassword = await bcrypt.hash(dto.password, BCRYPT_COST); - - // 3. Crear usuario - const user = this.userRepository.create({ - email: dto.email, - encrypted_password: hashedPassword, - role: dto.role || 'user', - }); - await this.userRepository.save(user); - - // 4. Crear perfil - const profile = this.profileRepository.create({ - id: user.id, - user_id: user.id, - email: user.email, - // ...otros campos del perfil - }); - await this.profileRepository.save(profile); - - // 5. Generar tokens y crear sesión - return this.createAuthResponse(user); - } - - /** - * Login de usuario - * - * @pattern - * 1. Buscar usuario por email - * 2. Validar password - * 3. Crear sesión - * 4. Generar tokens - */ - async login(email: string, password: string): Promise { - // 1. Buscar usuario - const user = await this.userRepository.findOne({ - where: { email }, - select: ['id', 'email', 'encrypted_password', 'role'], - }); - - if (!user) { - throw new UnauthorizedException('Credenciales inválidas'); - } - - // 2. Validar password - const isValid = await bcrypt.compare(password, user.encrypted_password); - if (!isValid) { - throw new UnauthorizedException('Credenciales inválidas'); - } - - // 3-4. Crear sesión y generar tokens - return this.createAuthResponse(user); - } - - /** - * Refresh de tokens - * - * @pattern - * 1. Buscar sesión por refresh token hash - * 2. Validar que no esté expirada - * 3. Generar nuevos tokens - * 4. Actualizar sesión - */ - async refreshToken(refreshToken: string): Promise { - const tokenHash = this.hashToken(refreshToken); - - const session = await this.sessionRepository.findOne({ - where: { refresh_token: tokenHash }, - relations: ['user'], - }); - - if (!session || session.is_revoked || new Date() > session.expires_at) { - throw new UnauthorizedException('Token inválido o expirado'); - } - - // Generar nuevos tokens - return this.createAuthResponse(session.user, session); - } - - /** - * Logout - * - * @pattern Revocar sesión actual - */ - async logout(sessionId: string): Promise { - await this.sessionRepository.update(sessionId, { is_revoked: true }); - } - - // ============ HELPERS PRIVADOS ============ - - /** - * Generar respuesta de autenticación completa - */ - private async createAuthResponse(user: User, existingSession?: UserSession): Promise { - const payload = { - sub: user.id, - email: user.email, - role: user.role, - }; - - const accessToken = this.jwtService.sign(payload, { expiresIn: ACCESS_TOKEN_EXPIRES }); - const refreshToken = crypto.randomBytes(32).toString('hex'); - - // Crear o actualizar sesión - if (existingSession) { - existingSession.refresh_token = this.hashToken(refreshToken); - existingSession.expires_at = this.calculateExpiry(REFRESH_TOKEN_EXPIRES); - await this.sessionRepository.save(existingSession); - } else { - const session = this.sessionRepository.create({ - user_id: user.id, - refresh_token: this.hashToken(refreshToken), - expires_at: this.calculateExpiry(REFRESH_TOKEN_EXPIRES), - }); - await this.sessionRepository.save(session); - } - - return { - accessToken, - refreshToken, - user: { - id: user.id, - email: user.email, - role: user.role, - }, - }; - } - - /** - * Hash de token para almacenamiento seguro - */ - private hashToken(token: string): string { - return crypto.createHash('sha256').update(token).digest('hex'); - } - - /** - * Calcular fecha de expiración - */ - private calculateExpiry(duration: string): Date { - const match = duration.match(/^(\d+)([mhd])$/); - if (!match) throw new Error('Invalid duration format'); - - const [, value, unit] = match; - const multipliers = { m: 60000, h: 3600000, d: 86400000 }; - - return new Date(Date.now() + parseInt(value) * multipliers[unit]); - } -} - -// ============ TIPOS ============ - -interface AuthResponse { - accessToken: string; - refreshToken: string; - user: { - id: string; - email: string; - role: string; - }; -} - -interface RegisterUserDto { - email: string; - password: string; - role?: string; -} diff --git a/shared/libs/auth/_reference/jwt-auth.guard.reference.ts b/shared/libs/auth/_reference/jwt-auth.guard.reference.ts deleted file mode 100644 index 7477d3cdc..000000000 --- a/shared/libs/auth/_reference/jwt-auth.guard.reference.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * JWT AUTH GUARD - REFERENCE IMPLEMENTATION - * - * @description Guard de autenticación JWT para proteger rutas. - * Activa la estrategia JWT y maneja errores de autenticación. - * - * @usage - * ```typescript - * @Get('protected') - * @UseGuards(JwtAuthGuard) - * getProtectedData(@Request() req) { - * return req.user; - * } - * ``` - * - * @origin gamilit/apps/backend/src/modules/auth/guards/jwt-auth.guard.ts - */ - -import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; -import { Reflector } from '@nestjs/core'; - -/** - * Metadata key para rutas públicas - */ -export const IS_PUBLIC_KEY = 'isPublic'; - -@Injectable() -export class JwtAuthGuard extends AuthGuard('jwt') { - constructor(private readonly reflector: Reflector) { - super(); - } - - /** - * Determinar si la ruta requiere autenticación - * - * @description Verifica el decorador @Public() antes de activar el guard. - * Si la ruta es pública, permite el acceso sin token. - */ - canActivate(context: ExecutionContext) { - // Verificar si la ruta tiene @Public() - const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ - context.getHandler(), - context.getClass(), - ]); - - if (isPublic) { - return true; - } - - // Activar validación JWT normal - return super.canActivate(context); - } - - /** - * Manejar resultado de autenticación - * - * @description Personaliza el mensaje de error cuando falla la autenticación. - */ - handleRequest(err: Error | null, user: any, info: Error | null) { - if (err || !user) { - if (info?.message === 'jwt expired') { - throw new UnauthorizedException('Token expirado'); - } - if (info?.message === 'No auth token') { - throw new UnauthorizedException('Token no proporcionado'); - } - throw new UnauthorizedException('No autorizado'); - } - return user; - } -} - -// ============ DECORADOR PUBLIC ============ - -import { SetMetadata } from '@nestjs/common'; - -/** - * Decorador para marcar rutas como públicas - * - * @usage - * ```typescript - * @Public() - * @Get('health') - * healthCheck() { - * return { status: 'ok' }; - * } - * ``` - */ -export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/shared/libs/auth/_reference/jwt.strategy.reference.ts b/shared/libs/auth/_reference/jwt.strategy.reference.ts deleted file mode 100644 index 045aea2ad..000000000 --- a/shared/libs/auth/_reference/jwt.strategy.reference.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * JWT STRATEGY - REFERENCE IMPLEMENTATION - * - * @description Estrategia de autenticación JWT para NestJS/Passport. - * Valida tokens JWT y extrae payload del usuario. - * - * @usage Copiar y adaptar según necesidades del proyecto. - * @origin gamilit/apps/backend/src/modules/auth/strategies/jwt.strategy.ts - */ - -import { Injectable, UnauthorizedException } from '@nestjs/common'; -import { PassportStrategy } from '@nestjs/passport'; -import { ExtractJwt, Strategy } from 'passport-jwt'; -import { ConfigService } from '@nestjs/config'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -// Adaptar import según proyecto -// import { User } from '../entities'; - -/** - * Payload del JWT - * - * @property sub - ID del usuario (auth.users.id) - * @property email - Email del usuario - * @property role - Rol del usuario - * @property iat - Issued at (timestamp) - * @property exp - Expiration (timestamp) - */ -interface JwtPayload { - sub: string; - email: string; - role: string; - iat: number; - exp: number; -} - -/** - * Usuario validado que se inyecta en req.user - */ -interface ValidatedUser { - id: string; - email: string; - role: string; -} - -@Injectable() -export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { - constructor( - private readonly configService: ConfigService, - @InjectRepository(User, 'auth') - private readonly userRepository: Repository, - ) { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - ignoreExpiration: false, - secretOrKey: configService.get('JWT_SECRET'), - }); - } - - /** - * Validar payload del JWT - * - * @description Este método es llamado por Passport después de verificar - * la firma y expiración del token. El valor retornado se asigna a req.user. - * - * @param payload - Payload decodificado del JWT - * @returns Usuario validado para inyectar en request - * @throws UnauthorizedException si el usuario no existe o está inactivo - */ - async validate(payload: JwtPayload): Promise { - // Opción 1: Solo validar que el payload es válido (más rápido) - // return { id: payload.sub, email: payload.email, role: payload.role }; - - // Opción 2: Verificar que el usuario existe en BD (más seguro) - const user = await this.userRepository.findOne({ - where: { id: payload.sub }, - select: ['id', 'email', 'role'], - }); - - if (!user) { - throw new UnauthorizedException('Usuario no encontrado'); - } - - // Opción 3: También verificar status (si aplica) - // if (user.status !== 'active') { - // throw new UnauthorizedException('Usuario inactivo'); - // } - - return { - id: user.id, - email: user.email, - role: user.role, - }; - } -} diff --git a/shared/libs/auth/_reference/roles.guard.reference.ts b/shared/libs/auth/_reference/roles.guard.reference.ts deleted file mode 100644 index fd89a332b..000000000 --- a/shared/libs/auth/_reference/roles.guard.reference.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * ROLES GUARD - REFERENCE IMPLEMENTATION - * - * @description Guard de autorización basado en roles (RBAC). - * Verifica que el usuario tenga uno de los roles requeridos. - * - * @usage - * ```typescript - * @Get('admin') - * @UseGuards(JwtAuthGuard, RolesGuard) - * @Roles('admin', 'super_admin') - * adminOnly(@Request() req) { - * return { message: 'Admin data' }; - * } - * ``` - * - * @origin gamilit/apps/backend/src/modules/auth/guards/roles.guard.ts - */ - -import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; - -/** - * Metadata key para roles requeridos - */ -export const ROLES_KEY = 'roles'; - -@Injectable() -export class RolesGuard implements CanActivate { - constructor(private readonly reflector: Reflector) {} - - /** - * Verificar si el usuario tiene los roles requeridos - * - * @description - * 1. Obtiene los roles requeridos del decorador @Roles() - * 2. Si no hay roles definidos, permite el acceso - * 3. Compara el rol del usuario con los roles permitidos - */ - canActivate(context: ExecutionContext): boolean { - // Obtener roles requeridos de handler y class - const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ - context.getHandler(), - context.getClass(), - ]); - - // Si no hay roles definidos, permitir acceso - if (!requiredRoles || requiredRoles.length === 0) { - return true; - } - - // Obtener usuario del request (inyectado por JwtAuthGuard) - const { user } = context.switchToHttp().getRequest(); - - if (!user || !user.role) { - throw new ForbiddenException('Usuario sin rol asignado'); - } - - // Verificar si el usuario tiene alguno de los roles requeridos - const hasRole = requiredRoles.includes(user.role); - - if (!hasRole) { - throw new ForbiddenException( - `Acceso denegado. Roles requeridos: ${requiredRoles.join(', ')}`, - ); - } - - return true; - } -} - -// ============ DECORADOR ROLES ============ - -import { SetMetadata } from '@nestjs/common'; - -/** - * Decorador para definir roles requeridos en una ruta - * - * @param roles - Lista de roles que pueden acceder - * - * @usage - * ```typescript - * @Roles('admin', 'teacher') - * @UseGuards(JwtAuthGuard, RolesGuard) - * @Get('teachers') - * getTeacherData() { ... } - * ``` - */ -export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); diff --git a/shared/libs/auth/_reference_frontend/auth-hooks.example.ts b/shared/libs/auth/_reference_frontend/auth-hooks.example.ts deleted file mode 100644 index e65ae3fa0..000000000 --- a/shared/libs/auth/_reference_frontend/auth-hooks.example.ts +++ /dev/null @@ -1,639 +0,0 @@ -/** - * AUTH HOOKS - REFERENCE IMPLEMENTATION (React) - * - * @description Hooks personalizados para autenticación en aplicaciones React. - * Incluye manejo de estado, persistencia, refresh automático y permisos. - * - * @usage - * ```tsx - * import { useAuth, useSession, usePermissions } from '@/hooks/auth'; - * - * function MyComponent() { - * const { user, login, logout, isAuthenticated } = useAuth(); - * const { session, refreshSession } = useSession(); - * const { can, hasRole } = usePermissions(); - * - * return ( - *
- * {isAuthenticated ? ( - *

Welcome {user.email}

- * ) : ( - * - * )} - *
- * ); - * } - * ``` - * - * @dependencies - * - react >= 18.0.0 - * - @tanstack/react-query (opcional, para caching) - * - zustand o context (para estado global) - * - * @origin Patrón base para aplicaciones React con autenticación - */ - -import { useState, useEffect, useCallback, useMemo } from 'react'; -import { useNavigate } from 'react-router-dom'; - -// ============ TIPOS ============ - -interface User { - id: string; - email: string; - role: string; - firstName?: string; - lastName?: string; - avatar?: string; -} - -interface AuthTokens { - accessToken: string; - refreshToken: string; -} - -interface LoginCredentials { - email: string; - password: string; -} - -interface RegisterData extends LoginCredentials { - firstName?: string; - lastName?: string; -} - -interface AuthState { - user: User | null; - tokens: AuthTokens | null; - isAuthenticated: boolean; - isLoading: boolean; - error: string | null; -} - -interface Permission { - resource: string; - action: string; // 'create' | 'read' | 'update' | 'delete' -} - -// ============ CONFIGURACIÓN ============ - -const AUTH_CONFIG = { - API_BASE_URL: process.env.REACT_APP_API_URL || 'http://localhost:3000', - STORAGE_KEY: 'auth_tokens', - REFRESH_INTERVAL: 14 * 60 * 1000, // 14 minutos (antes de expirar el access token) - TOKEN_EXPIRY_BUFFER: 60 * 1000, // 1 minuto de buffer -}; - -// ============ UTILIDADES DE STORAGE ============ - -const storage = { - get: (): AuthTokens | null => { - const data = localStorage.getItem(AUTH_CONFIG.STORAGE_KEY); - return data ? JSON.parse(data) : null; - }, - set: (tokens: AuthTokens): void => { - localStorage.setItem(AUTH_CONFIG.STORAGE_KEY, JSON.stringify(tokens)); - }, - clear: (): void => { - localStorage.removeItem(AUTH_CONFIG.STORAGE_KEY); - }, -}; - -// ============ API CLIENT ============ - -class AuthAPI { - private baseURL = AUTH_CONFIG.API_BASE_URL; - - private async request( - endpoint: string, - options: RequestInit = {}, - ): Promise { - const url = `${this.baseURL}${endpoint}`; - const headers = { - 'Content-Type': 'application/json', - ...options.headers, - }; - - const response = await fetch(url, { ...options, headers }); - - if (!response.ok) { - const error = await response.json().catch(() => ({ message: 'Request failed' })); - throw new Error(error.message || 'Request failed'); - } - - return response.json(); - } - - async login(credentials: LoginCredentials): Promise<{ user: User; tokens: AuthTokens }> { - return this.request('/auth/login', { - method: 'POST', - body: JSON.stringify(credentials), - }); - } - - async register(data: RegisterData): Promise<{ user: User; tokens: AuthTokens }> { - return this.request('/auth/register', { - method: 'POST', - body: JSON.stringify(data), - }); - } - - async logout(accessToken: string): Promise { - return this.request('/auth/logout', { - method: 'POST', - headers: { Authorization: `Bearer ${accessToken}` }, - }); - } - - async refreshToken(refreshToken: string): Promise { - return this.request('/auth/refresh', { - method: 'POST', - body: JSON.stringify({ refreshToken }), - }); - } - - async getProfile(accessToken: string): Promise { - return this.request('/auth/profile', { - headers: { Authorization: `Bearer ${accessToken}` }, - }); - } -} - -const authAPI = new AuthAPI(); - -// ============ HOOK: useAuth ============ - -/** - * Hook principal de autenticación - * - * @example - * ```tsx - * const { user, login, logout, register, isAuthenticated, isLoading } = useAuth(); - * - * const handleLogin = async () => { - * try { - * await login({ email: 'user@example.com', password: 'password' }); - * navigate('/dashboard'); - * } catch (error) { - * console.error('Login failed:', error); - * } - * }; - * ``` - */ -export function useAuth() { - const [state, setState] = useState({ - user: null, - tokens: null, - isAuthenticated: false, - isLoading: true, - error: null, - }); - - const navigate = useNavigate(); - - // Cargar usuario inicial desde tokens almacenados - useEffect(() => { - const initAuth = async () => { - const tokens = storage.get(); - - if (!tokens) { - setState((prev) => ({ ...prev, isLoading: false })); - return; - } - - try { - const user = await authAPI.getProfile(tokens.accessToken); - setState({ - user, - tokens, - isAuthenticated: true, - isLoading: false, - error: null, - }); - } catch (error) { - // Token inválido o expirado - intentar refresh - try { - const newTokens = await authAPI.refreshToken(tokens.refreshToken); - storage.set(newTokens); - const user = await authAPI.getProfile(newTokens.accessToken); - setState({ - user, - tokens: newTokens, - isAuthenticated: true, - isLoading: false, - error: null, - }); - } catch (refreshError) { - // Refresh falló - limpiar todo - storage.clear(); - setState({ - user: null, - tokens: null, - isAuthenticated: false, - isLoading: false, - error: null, - }); - } - } - }; - - initAuth(); - }, []); - - const login = useCallback(async (credentials: LoginCredentials) => { - setState((prev) => ({ ...prev, isLoading: true, error: null })); - - try { - const { user, tokens } = await authAPI.login(credentials); - storage.set(tokens); - setState({ - user, - tokens, - isAuthenticated: true, - isLoading: false, - error: null, - }); - } catch (error) { - setState((prev) => ({ - ...prev, - isLoading: false, - error: error instanceof Error ? error.message : 'Login failed', - })); - throw error; - } - }, []); - - const register = useCallback(async (data: RegisterData) => { - setState((prev) => ({ ...prev, isLoading: true, error: null })); - - try { - const { user, tokens } = await authAPI.register(data); - storage.set(tokens); - setState({ - user, - tokens, - isAuthenticated: true, - isLoading: false, - error: null, - }); - } catch (error) { - setState((prev) => ({ - ...prev, - isLoading: false, - error: error instanceof Error ? error.message : 'Registration failed', - })); - throw error; - } - }, []); - - const logout = useCallback(async () => { - if (state.tokens) { - try { - await authAPI.logout(state.tokens.accessToken); - } catch (error) { - // Ignorar error de logout - limpiar estado de todos modos - console.error('Logout error:', error); - } - } - - storage.clear(); - setState({ - user: null, - tokens: null, - isAuthenticated: false, - isLoading: false, - error: null, - }); - - navigate('/login'); - }, [state.tokens, navigate]); - - const updateUser = useCallback((updates: Partial) => { - setState((prev) => ({ - ...prev, - user: prev.user ? { ...prev.user, ...updates } : null, - })); - }, []); - - return { - user: state.user, - tokens: state.tokens, - isAuthenticated: state.isAuthenticated, - isLoading: state.isLoading, - error: state.error, - login, - register, - logout, - updateUser, - }; -} - -// ============ HOOK: useSession ============ - -/** - * Hook para gestión de sesión y refresh automático de tokens - * - * @example - * ```tsx - * const { session, isValid, expiresIn, refreshSession } = useSession(); - * - * useEffect(() => { - * if (expiresIn < 60000) { // Menos de 1 minuto - * refreshSession(); - * } - * }, [expiresIn]); - * ``` - */ -export function useSession() { - const [tokens, setTokens] = useState(storage.get()); - const [lastRefresh, setLastRefresh] = useState(null); - - // Refresh automático - useEffect(() => { - if (!tokens) return; - - const interval = setInterval(async () => { - try { - const newTokens = await authAPI.refreshToken(tokens.refreshToken); - storage.set(newTokens); - setTokens(newTokens); - setLastRefresh(new Date()); - } catch (error) { - console.error('Auto-refresh failed:', error); - // Si el refresh falla, el usuario será redirigido al login en el próximo request - } - }, AUTH_CONFIG.REFRESH_INTERVAL); - - return () => clearInterval(interval); - }, [tokens]); - - const refreshSession = useCallback(async () => { - if (!tokens) { - throw new Error('No active session'); - } - - try { - const newTokens = await authAPI.refreshToken(tokens.refreshToken); - storage.set(newTokens); - setTokens(newTokens); - setLastRefresh(new Date()); - return newTokens; - } catch (error) { - storage.clear(); - setTokens(null); - throw error; - } - }, [tokens]); - - const expiresIn = useMemo(() => { - if (!tokens || !lastRefresh) return null; - const elapsed = Date.now() - lastRefresh.getTime(); - return AUTH_CONFIG.REFRESH_INTERVAL - elapsed; - }, [tokens, lastRefresh]); - - return { - session: tokens, - isValid: !!tokens, - expiresIn, - lastRefresh, - refreshSession, - }; -} - -// ============ HOOK: usePermissions ============ - -/** - * Hook para control de permisos y roles (RBAC) - * - * @example - * ```tsx - * const { can, hasRole, hasAnyRole, hasAllRoles } = usePermissions(); - * - * if (can('users', 'delete')) { - * return ; - * } - * - * if (hasRole('admin')) { - * return ; - * } - * ``` - */ -export function usePermissions() { - const { user } = useAuth(); - - // Mapeo de roles a permisos (adaptar según proyecto) - const rolePermissions: Record = { - admin: [ - { resource: '*', action: '*' }, // Admin tiene todos los permisos - ], - moderator: [ - { resource: 'users', action: 'read' }, - { resource: 'users', action: 'update' }, - { resource: 'posts', action: '*' }, - { resource: 'comments', action: '*' }, - ], - user: [ - { resource: 'profile', action: 'read' }, - { resource: 'profile', action: 'update' }, - { resource: 'posts', action: 'create' }, - { resource: 'posts', action: 'read' }, - ], - }; - - const getUserPermissions = useCallback((): Permission[] => { - if (!user) return []; - return rolePermissions[user.role] || []; - }, [user]); - - const can = useCallback( - (resource: string, action: string): boolean => { - const permissions = getUserPermissions(); - - return permissions.some( - (perm) => - (perm.resource === '*' || perm.resource === resource) && - (perm.action === '*' || perm.action === action), - ); - }, - [getUserPermissions], - ); - - const hasRole = useCallback( - (role: string): boolean => { - return user?.role === role; - }, - [user], - ); - - const hasAnyRole = useCallback( - (roles: string[]): boolean => { - return roles.some((role) => user?.role === role); - }, - [user], - ); - - const hasAllRoles = useCallback( - (roles: string[]): boolean => { - // En un sistema simple con un solo role por usuario, esto solo funciona con un role - // En sistemas complejos con múltiples roles, adaptar lógica - return roles.length === 1 && user?.role === roles[0]; - }, - [user], - ); - - return { - can, - hasRole, - hasAnyRole, - hasAllRoles, - permissions: getUserPermissions(), - }; -} - -// ============ COMPONENTES DE UTILIDAD ============ - -/** - * Componente de protección por permisos - * - * @example - * ```tsx - * - * - * - * ``` - */ -export function RequirePermission({ - resource, - action, - fallback = null, - children, -}: { - resource: string; - action: string; - fallback?: React.ReactNode; - children: React.ReactNode; -}) { - const { can } = usePermissions(); - - if (!can(resource, action)) { - return <>{fallback}; - } - - return <>{children}; -} - -/** - * Componente de protección por rol - * - * @example - * ```tsx - * - * - * - * - * - * - * - * ``` - */ -export function RequireRole({ - role, - roles, - requireAll = false, - fallback = null, - children, -}: { - role?: string; - roles?: string[]; - requireAll?: boolean; - fallback?: React.ReactNode; - children: React.ReactNode; -}) { - const { hasRole, hasAnyRole, hasAllRoles } = usePermissions(); - - let hasAccess = false; - - if (role) { - hasAccess = hasRole(role); - } else if (roles) { - hasAccess = requireAll ? hasAllRoles(roles) : hasAnyRole(roles); - } - - if (!hasAccess) { - return <>{fallback}; - } - - return <>{children}; -} - -/** - * HOC para proteger rutas - * - * @example - * ```tsx - * const ProtectedDashboard = withAuth(Dashboard); - * const AdminPanel = withAuth(AdminPanelComponent, { role: 'admin' }); - * ``` - */ -export function withAuth

( - Component: React.ComponentType

, - options?: { - role?: string; - roles?: string[]; - requireAll?: boolean; - redirectTo?: string; - }, -) { - return function WithAuthComponent(props: P) { - const { isAuthenticated, isLoading } = useAuth(); - const { hasRole, hasAnyRole, hasAllRoles } = usePermissions(); - const navigate = useNavigate(); - - useEffect(() => { - if (isLoading) return; - - if (!isAuthenticated) { - navigate(options?.redirectTo || '/login'); - return; - } - - if (options?.role && !hasRole(options.role)) { - navigate('/unauthorized'); - return; - } - - if (options?.roles) { - const hasAccess = options.requireAll - ? hasAllRoles(options.roles) - : hasAnyRole(options.roles); - - if (!hasAccess) { - navigate('/unauthorized'); - } - } - }, [isAuthenticated, isLoading, navigate, hasRole, hasAnyRole, hasAllRoles]); - - if (isLoading) { - return

Loading...
; - } - - if (!isAuthenticated) { - return null; - } - - return ; - }; -} - -// ============ EXPORTS ============ - -export type { - User, - AuthTokens, - LoginCredentials, - RegisterData, - AuthState, - Permission, -}; - -export { authAPI, storage }; diff --git a/shared/libs/feature-flags/IMPLEMENTATION.md b/shared/libs/feature-flags/IMPLEMENTATION.md deleted file mode 100644 index e917bde52..000000000 --- a/shared/libs/feature-flags/IMPLEMENTATION.md +++ /dev/null @@ -1,912 +0,0 @@ -# Guía de Implementación: Feature Flags - -**Versión:** 1.0.0 -**Tiempo estimado:** 1-2 horas -**Complejidad:** Media - ---- - -## Pre-requisitos - -- [ ] Proyecto NestJS existente -- [ ] TypeORM configurado -- [ ] PostgreSQL como base de datos - ---- - -## Paso 1: Crear Estructura de Directorios - -```bash -mkdir -p src/modules/feature-flags/entities -mkdir -p src/modules/feature-flags/services -mkdir -p src/modules/feature-flags/controllers -mkdir -p src/modules/feature-flags/dto -mkdir -p src/modules/feature-flags/guards -mkdir -p src/modules/feature-flags/decorators -``` - ---- - -## Paso 2: Crear Entidad FeatureFlag - -```typescript -// src/modules/feature-flags/entities/feature-flag.entity.ts -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - Index, - Check, -} from 'typeorm'; - -@Entity({ schema: 'config', name: 'feature_flags' }) -@Index('idx_feature_flags_key', ['featureKey']) -@Index('idx_feature_flags_enabled', ['isEnabled'], { where: '"is_enabled" = true' }) -@Check('"rollout_percentage" >= 0 AND "rollout_percentage" <= 100') -export class FeatureFlag { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ name: 'tenant_id', type: 'uuid', nullable: true }) - tenantId?: string; - - @Column({ name: 'feature_key', type: 'varchar', length: 100, unique: true }) - featureKey: string; - - @Column({ name: 'feature_name', type: 'varchar', length: 255 }) - featureName: string; - - @Column({ type: 'text', nullable: true }) - description?: string; - - @Column({ name: 'is_enabled', type: 'boolean', default: false }) - isEnabled: boolean; - - @Column({ name: 'rollout_percentage', type: 'integer', default: 0 }) - rolloutPercentage: number; - - @Column({ name: 'target_users', type: 'uuid', array: true, nullable: true }) - targetUsers?: string[]; - - @Column({ name: 'target_roles', type: 'varchar', array: true, nullable: true }) - targetRoles?: string[]; - - @Column({ name: 'target_conditions', type: 'jsonb', default: {} }) - targetConditions: Record; - - @Column({ name: 'starts_at', type: 'timestamp with time zone', nullable: true }) - startsAt?: Date; - - @Column({ name: 'ends_at', type: 'timestamp with time zone', nullable: true }) - endsAt?: Date; - - @Column({ type: 'jsonb', default: {} }) - metadata: Record; - - @Column({ name: 'created_by', type: 'uuid', nullable: true }) - createdBy?: string; - - @Column({ name: 'updated_by', type: 'uuid', nullable: true }) - updatedBy?: string; - - @CreateDateColumn({ name: 'created_at' }) - createdAt: Date; - - @UpdateDateColumn({ name: 'updated_at' }) - updatedAt: Date; -} -``` - ---- - -## Paso 3: Crear DTOs - -```typescript -// src/modules/feature-flags/dto/create-feature-flag.dto.ts -import { - IsString, - IsBoolean, - IsOptional, - IsInt, - Min, - Max, - IsArray, - IsObject, - MaxLength, - Matches, - IsDateString, -} from 'class-validator'; - -export class CreateFeatureFlagDto { - @IsString() - @MaxLength(100) - @Matches(/^[a-z][a-z0-9_]*$/, { - message: 'Key debe ser snake_case y comenzar con letra', - }) - key: string; - - @IsString() - @MaxLength(255) - name: string; - - @IsString() - @IsOptional() - description?: string; - - @IsBoolean() - @IsOptional() - isEnabled?: boolean; - - @IsInt() - @Min(0) - @Max(100) - @IsOptional() - rolloutPercentage?: number; - - @IsArray() - @IsString({ each: true }) - @IsOptional() - targetUsers?: string[]; - - @IsArray() - @IsString({ each: true }) - @IsOptional() - targetRoles?: string[]; - - @IsObject() - @IsOptional() - targetConditions?: Record; - - @IsDateString() - @IsOptional() - startsAt?: string; - - @IsDateString() - @IsOptional() - endsAt?: string; - - @IsString() - @IsOptional() - category?: string; - - @IsObject() - @IsOptional() - metadata?: Record; -} - -// src/modules/feature-flags/dto/update-feature-flag.dto.ts -import { PartialType } from '@nestjs/mapped-types'; -import { CreateFeatureFlagDto } from './create-feature-flag.dto'; - -export class UpdateFeatureFlagDto extends PartialType(CreateFeatureFlagDto) {} - -// src/modules/feature-flags/dto/feature-flag-query.dto.ts -import { IsBoolean, IsOptional, IsString } from 'class-validator'; -import { Transform } from 'class-transformer'; - -export class FeatureFlagQueryDto { - @IsBoolean() - @IsOptional() - @Transform(({ value }) => value === 'true') - isEnabled?: boolean; - - @IsString() - @IsOptional() - category?: string; -} - -// src/modules/feature-flags/dto/check-result.dto.ts -export class FeatureFlagCheckResultDto { - enabled: boolean; - reason: string; -} - -// src/modules/feature-flags/dto/index.ts -export * from './create-feature-flag.dto'; -export * from './update-feature-flag.dto'; -export * from './feature-flag-query.dto'; -export * from './check-result.dto'; -``` - ---- - -## Paso 4: Crear Servicio - -```typescript -// src/modules/feature-flags/services/feature-flags.service.ts -import { - Injectable, - NotFoundException, - ConflictException, -} from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { createHash } from 'crypto'; -import { FeatureFlag } from '../entities/feature-flag.entity'; -import { - CreateFeatureFlagDto, - UpdateFeatureFlagDto, - FeatureFlagQueryDto, - FeatureFlagCheckResultDto, -} from '../dto'; - -@Injectable() -export class FeatureFlagsService { - constructor( - @InjectRepository(FeatureFlag) - private readonly featureFlagRepo: Repository, - ) {} - - /** - * Obtener todas las feature flags con filtros opcionales - */ - async findAll(query?: FeatureFlagQueryDto): Promise { - const qb = this.featureFlagRepo.createQueryBuilder('ff'); - - if (query?.isEnabled !== undefined) { - qb.andWhere('ff.is_enabled = :isEnabled', { isEnabled: query.isEnabled }); - } - - if (query?.category) { - qb.andWhere("ff.metadata->>'category' = :category", { - category: query.category, - }); - } - - return qb.orderBy('ff.feature_name', 'ASC').getMany(); - } - - /** - * Obtener una feature flag por su key - */ - async findOne(key: string): Promise { - const flag = await this.featureFlagRepo.findOne({ - where: { featureKey: key }, - }); - - if (!flag) { - throw new NotFoundException(`Feature flag "${key}" no encontrada`); - } - - return flag; - } - - /** - * Crear una nueva feature flag - */ - async create(dto: CreateFeatureFlagDto, createdBy?: string): Promise { - const existing = await this.featureFlagRepo.findOne({ - where: { featureKey: dto.key }, - }); - - if (existing) { - throw new ConflictException(`Feature flag "${dto.key}" ya existe`); - } - - const flag = this.featureFlagRepo.create({ - featureKey: dto.key, - featureName: dto.name, - description: dto.description, - isEnabled: dto.isEnabled ?? false, - rolloutPercentage: dto.rolloutPercentage ?? 0, - targetUsers: dto.targetUsers, - targetRoles: dto.targetRoles, - targetConditions: dto.targetConditions ?? {}, - startsAt: dto.startsAt ? new Date(dto.startsAt) : undefined, - endsAt: dto.endsAt ? new Date(dto.endsAt) : undefined, - metadata: { - ...dto.metadata, - category: dto.category, - }, - createdBy, - }); - - return this.featureFlagRepo.save(flag); - } - - /** - * Actualizar una feature flag existente - */ - async update( - key: string, - dto: UpdateFeatureFlagDto, - updatedBy?: string, - ): Promise { - const flag = await this.findOne(key); - - if (dto.name !== undefined) flag.featureName = dto.name; - if (dto.description !== undefined) flag.description = dto.description; - if (dto.isEnabled !== undefined) flag.isEnabled = dto.isEnabled; - if (dto.rolloutPercentage !== undefined) flag.rolloutPercentage = dto.rolloutPercentage; - if (dto.targetUsers !== undefined) flag.targetUsers = dto.targetUsers; - if (dto.targetRoles !== undefined) flag.targetRoles = dto.targetRoles; - if (dto.targetConditions !== undefined) flag.targetConditions = dto.targetConditions; - if (dto.startsAt !== undefined) flag.startsAt = new Date(dto.startsAt); - if (dto.endsAt !== undefined) flag.endsAt = new Date(dto.endsAt); - - if (dto.metadata !== undefined || dto.category !== undefined) { - flag.metadata = { - ...flag.metadata, - ...dto.metadata, - ...(dto.category && { category: dto.category }), - }; - } - - flag.updatedBy = updatedBy; - - return this.featureFlagRepo.save(flag); - } - - /** - * Eliminar una feature flag - */ - async remove(key: string): Promise { - const flag = await this.findOne(key); - await this.featureFlagRepo.remove(flag); - } - - /** - * Verificar si una feature está habilitada para un usuario - */ - async isEnabled( - key: string, - userId?: string, - userRoles?: string[], - ): Promise { - try { - const flag = await this.findOne(key); - - // 1. Feature habilitada globalmente? - if (!flag.isEnabled) { - return { enabled: false, reason: 'Feature deshabilitada globalmente' }; - } - - // 2. Verificar período de validez - const now = new Date(); - if (flag.startsAt && now < flag.startsAt) { - return { enabled: false, reason: 'Feature no ha iniciado aún' }; - } - if (flag.endsAt && now > flag.endsAt) { - return { enabled: false, reason: 'Feature ha expirado' }; - } - - // 3. Usuario en target_users (early access)? - if (userId && flag.targetUsers?.length > 0) { - if (flag.targetUsers.includes(userId)) { - return { enabled: true, reason: 'Usuario en lista de early access' }; - } - } - - // 4. Usuario tiene target_role? - if (userRoles && flag.targetRoles?.length > 0) { - const hasRole = userRoles.some((role) => flag.targetRoles?.includes(role)); - if (hasRole) { - return { enabled: true, reason: 'Usuario tiene rol objetivo' }; - } - } - - // 5. Rollout 100%? - if (flag.rolloutPercentage === 100) { - return { enabled: true, reason: 'Rollout al 100%' }; - } - - // 6. Rollout 0%? - if (flag.rolloutPercentage === 0) { - return { enabled: false, reason: 'Rollout al 0%' }; - } - - // 7. Hash del userId para rollout gradual - if (userId) { - const hash = this.hashUserId(userId, key); - const isInRollout = hash < flag.rolloutPercentage; - return { - enabled: isInRollout, - reason: isInRollout - ? `Usuario en grupo de rollout ${flag.rolloutPercentage}%` - : `Usuario fuera del grupo de rollout ${flag.rolloutPercentage}%`, - }; - } - - // Sin userId, usar probabilidad - return { - enabled: Math.random() * 100 < flag.rolloutPercentage, - reason: `Selección aleatoria basada en ${flag.rolloutPercentage}%`, - }; - } catch (error) { - if (error instanceof NotFoundException) { - return { enabled: false, reason: 'Feature flag no encontrada' }; - } - throw error; - } - } - - /** - * Hash consistente para rollout gradual - * Retorna número entre 0 y 100 - */ - private hashUserId(userId: string, featureKey: string): number { - const hash = createHash('sha256') - .update(`${userId}:${featureKey}`) - .digest('hex'); - - const hashInt = parseInt(hash.substring(0, 8), 16); - return hashInt % 101; - } - - /** - * Habilitar feature flag - */ - async enable(key: string, updatedBy?: string): Promise { - return this.update(key, { isEnabled: true }, updatedBy); - } - - /** - * Deshabilitar feature flag - */ - async disable(key: string, updatedBy?: string): Promise { - return this.update(key, { isEnabled: false }, updatedBy); - } - - /** - * Actualizar porcentaje de rollout - */ - async updateRollout( - key: string, - percentage: number, - updatedBy?: string, - ): Promise { - return this.update(key, { rolloutPercentage: percentage }, updatedBy); - } -} -``` - ---- - -## Paso 5: Crear Guard - -```typescript -// src/modules/feature-flags/guards/feature-flag.guard.ts -import { - Injectable, - CanActivate, - ExecutionContext, - ForbiddenException, -} from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { FeatureFlagsService } from '../services/feature-flags.service'; -import { FEATURE_FLAG_KEY } from '../decorators/feature-flag.decorator'; - -@Injectable() -export class FeatureFlagGuard implements CanActivate { - constructor( - private reflector: Reflector, - private featureFlagsService: FeatureFlagsService, - ) {} - - async canActivate(context: ExecutionContext): Promise { - const featureKey = this.reflector.getAllAndOverride( - FEATURE_FLAG_KEY, - [context.getHandler(), context.getClass()], - ); - - if (!featureKey) { - return true; // No feature flag requerida - } - - const request = context.switchToHttp().getRequest(); - const userId = request.user?.id; - const userRoles = request.user?.roles || []; - - const result = await this.featureFlagsService.isEnabled( - featureKey, - userId, - userRoles, - ); - - if (!result.enabled) { - throw new ForbiddenException( - `Feature "${featureKey}" no disponible: ${result.reason}`, - ); - } - - return true; - } -} -``` - ---- - -## Paso 6: Crear Decoradores - -```typescript -// src/modules/feature-flags/decorators/feature-flag.decorator.ts -import { SetMetadata } from '@nestjs/common'; - -export const FEATURE_FLAG_KEY = 'feature_flag'; - -/** - * Decorador para marcar rutas que requieren una feature flag - * @param key - Clave de la feature flag - */ -export const FeatureFlag = (key: string) => SetMetadata(FEATURE_FLAG_KEY, key); - -// src/modules/feature-flags/decorators/check-feature.decorator.ts -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; -import { FeatureFlagsService } from '../services/feature-flags.service'; - -/** - * Decorador de parámetro para verificar feature inline - * Nota: Este decorador requiere inyección manual del servicio - */ -export const CheckFeature = createParamDecorator( - async (featureKey: string, ctx: ExecutionContext): Promise => { - // Nota: Los decoradores de parámetro no pueden inyectar servicios directamente - // Este decorador retorna un placeholder, la verificación real se hace en el servicio - return featureKey; - }, -); -``` - ---- - -## Paso 7: Crear Controller - -```typescript -// src/modules/feature-flags/controllers/feature-flags.controller.ts -import { - Controller, - Get, - Post, - Put, - Delete, - Body, - Param, - Query, - UseGuards, - Request, -} from '@nestjs/common'; -import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; -import { AdminGuard } from '../../auth/guards/admin.guard'; -import { FeatureFlagsService } from '../services/feature-flags.service'; -import { - CreateFeatureFlagDto, - UpdateFeatureFlagDto, - FeatureFlagQueryDto, - FeatureFlagCheckResultDto, -} from '../dto'; -import { FeatureFlag } from '../entities/feature-flag.entity'; - -@Controller('admin/feature-flags') -@UseGuards(JwtAuthGuard, AdminGuard) -export class FeatureFlagsController { - constructor(private readonly featureFlagsService: FeatureFlagsService) {} - - @Get() - async findAll(@Query() query: FeatureFlagQueryDto): Promise { - return this.featureFlagsService.findAll(query); - } - - @Get(':key') - async findOne(@Param('key') key: string): Promise { - return this.featureFlagsService.findOne(key); - } - - @Post() - async create( - @Body() dto: CreateFeatureFlagDto, - @Request() req: any, - ): Promise { - return this.featureFlagsService.create(dto, req.user?.id); - } - - @Put(':key') - async update( - @Param('key') key: string, - @Body() dto: UpdateFeatureFlagDto, - @Request() req: any, - ): Promise { - return this.featureFlagsService.update(key, dto, req.user?.id); - } - - @Delete(':key') - async remove(@Param('key') key: string): Promise { - return this.featureFlagsService.remove(key); - } - - @Post(':key/enable') - async enable( - @Param('key') key: string, - @Request() req: any, - ): Promise { - return this.featureFlagsService.enable(key, req.user?.id); - } - - @Post(':key/disable') - async disable( - @Param('key') key: string, - @Request() req: any, - ): Promise { - return this.featureFlagsService.disable(key, req.user?.id); - } - - @Put(':key/rollout') - async updateRollout( - @Param('key') key: string, - @Body('percentage') percentage: number, - @Request() req: any, - ): Promise { - return this.featureFlagsService.updateRollout(key, percentage, req.user?.id); - } -} - -// Controller público para verificar features -@Controller('feature-flags') -export class FeatureFlagsPublicController { - constructor(private readonly featureFlagsService: FeatureFlagsService) {} - - @Post(':key/check') - @UseGuards(JwtAuthGuard) - async checkFeature( - @Param('key') key: string, - @Request() req: any, - ): Promise { - return this.featureFlagsService.isEnabled( - key, - req.user?.id, - req.user?.roles, - ); - } -} -``` - ---- - -## Paso 8: Crear Módulo - -```typescript -// src/modules/feature-flags/feature-flags.module.ts -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { FeatureFlag } from './entities/feature-flag.entity'; -import { FeatureFlagsService } from './services/feature-flags.service'; -import { - FeatureFlagsController, - FeatureFlagsPublicController, -} from './controllers/feature-flags.controller'; -import { FeatureFlagGuard } from './guards/feature-flag.guard'; - -@Module({ - imports: [TypeOrmModule.forFeature([FeatureFlag])], - controllers: [FeatureFlagsController, FeatureFlagsPublicController], - providers: [FeatureFlagsService, FeatureFlagGuard], - exports: [FeatureFlagsService, FeatureFlagGuard], -}) -export class FeatureFlagsModule {} -``` - ---- - -## Paso 9: Registrar en AppModule - -```typescript -// src/app.module.ts -import { Module } from '@nestjs/common'; -import { FeatureFlagsModule } from './modules/feature-flags/feature-flags.module'; - -@Module({ - imports: [ - // ... otros módulos - FeatureFlagsModule, - ], -}) -export class AppModule {} -``` - ---- - -## Paso 10: Migraciones SQL - -```sql --- migrations/001_create_feature_flags.sql -CREATE SCHEMA IF NOT EXISTS config; - -CREATE TABLE config.feature_flags ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID, - feature_key VARCHAR(100) UNIQUE NOT NULL, - feature_name VARCHAR(255) NOT NULL, - description TEXT, - is_enabled BOOLEAN DEFAULT false, - rollout_percentage INTEGER DEFAULT 0 CHECK (rollout_percentage >= 0 AND rollout_percentage <= 100), - target_users UUID[], - target_roles VARCHAR(50)[], - target_conditions JSONB DEFAULT '{}', - starts_at TIMESTAMP WITH TIME ZONE, - ends_at TIMESTAMP WITH TIME ZONE, - metadata JSONB DEFAULT '{}', - created_by UUID, - updated_by UUID, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); - --- Índices -CREATE INDEX idx_feature_flags_key ON config.feature_flags(feature_key); -CREATE INDEX idx_feature_flags_enabled ON config.feature_flags(is_enabled) WHERE is_enabled = true; -CREATE INDEX idx_feature_flags_dates ON config.feature_flags(starts_at, ends_at) WHERE is_enabled = true; - --- Trigger para updated_at -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = CURRENT_TIMESTAMP; - RETURN NEW; -END; -$$ language 'plpgsql'; - -CREATE TRIGGER update_feature_flags_updated_at - BEFORE UPDATE ON config.feature_flags - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); -``` - ---- - -## Paso 11: Uso en Código - -### Verificación programática - -```typescript -// En cualquier servicio -@Injectable() -export class CheckoutService { - constructor( - private readonly featureFlagsService: FeatureFlagsService, - ) {} - - async processCheckout(userId: string, cart: Cart) { - const useNewCheckout = await this.featureFlagsService.isEnabled( - 'new_checkout_flow', - userId, - ); - - if (useNewCheckout.enabled) { - return this.processNewCheckout(cart); - } - return this.processLegacyCheckout(cart); - } -} -``` - -### Proteger rutas con Guard - -```typescript -@Controller('experiments') -@UseGuards(JwtAuthGuard) -export class ExperimentsController { - @Get('new-dashboard') - @UseGuards(FeatureFlagGuard) - @FeatureFlag('new_dashboard') - async getNewDashboard() { - return { message: 'Bienvenido al nuevo dashboard!' }; - } -} -``` - -### Helper para frontend - -```typescript -// src/modules/feature-flags/feature-flags.helper.ts -import { FeatureFlagsService } from './services/feature-flags.service'; - -export async function getFeatureFlagsForUser( - service: FeatureFlagsService, - userId: string, - userRoles: string[], - featureKeys: string[], -): Promise> { - const results: Record = {}; - - await Promise.all( - featureKeys.map(async (key) => { - const result = await service.isEnabled(key, userId, userRoles); - results[key] = result.enabled; - }), - ); - - return results; -} - -// Uso: endpoint que retorna todas las features del usuario -@Get('my-features') -@UseGuards(JwtAuthGuard) -async getMyFeatures(@Request() req: any) { - const keys = ['new_checkout', 'dark_mode', 'beta_features']; - return getFeatureFlagsForUser( - this.featureFlagsService, - req.user.id, - req.user.roles, - keys, - ); -} -``` - ---- - -## Variables de Entorno - -```env -# Feature Flags (opcional) -FEATURE_FLAGS_CACHE_TTL=300 -FEATURE_FLAGS_DEFAULT=false -``` - ---- - -## Checklist de Implementación - -- [ ] Entidad FeatureFlag creada -- [ ] DTOs de validación creados -- [ ] FeatureFlagsService implementado con hash consistente -- [ ] FeatureFlagGuard creado -- [ ] Decorador @FeatureFlag creado -- [ ] Controllers (admin y público) implementados -- [ ] Módulo registrado en AppModule -- [ ] Migración SQL ejecutada -- [ ] Build pasa sin errores -- [ ] Test: crear flag y verificar isEnabled - ---- - -## Verificar Funcionamiento - -```bash -# 1. Crear feature flag -curl -X POST http://localhost:3000/api/admin/feature-flags \ - -H "Authorization: Bearer $ADMIN_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "key": "test_feature", - "name": "Test Feature", - "isEnabled": true, - "rolloutPercentage": 50 - }' - -# 2. Verificar si está habilitada para un usuario -curl -X POST http://localhost:3000/api/feature-flags/test_feature/check \ - -H "Authorization: Bearer $USER_TOKEN" - -# 3. Actualizar rollout -curl -X PUT http://localhost:3000/api/admin/feature-flags/test_feature/rollout \ - -H "Authorization: Bearer $ADMIN_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"percentage": 100}' - -# 4. Deshabilitar -curl -X POST http://localhost:3000/api/admin/feature-flags/test_feature/disable \ - -H "Authorization: Bearer $ADMIN_TOKEN" -``` - ---- - -## Troubleshooting - -### Feature siempre retorna false -- Verificar que `is_enabled = true` -- Verificar que `rollout_percentage > 0` o usuario en `target_users` -- Verificar período de validez (`starts_at`, `ends_at`) - -### Rollout no es consistente -- Verificar que se pase el mismo `userId` -- El hash depende de `userId + featureKey` - -### Guard no funciona -- Verificar que `FeatureFlagGuard` esté registrado en providers -- Verificar que `@FeatureFlag('key')` esté en el handler - ---- - -**Versión:** 1.0.0 -**Sistema:** SIMCO Catálogo diff --git a/shared/libs/feature-flags/README.md b/shared/libs/feature-flags/README.md deleted file mode 100644 index c7b9c4a02..000000000 --- a/shared/libs/feature-flags/README.md +++ /dev/null @@ -1,360 +0,0 @@ -# Feature Flags Dinámicos - -**Versión:** 1.0.0 -**Origen:** projects/gamilit -**Estado:** Producción -**Última actualización:** 2025-12-08 - ---- - -## Descripción - -Sistema de feature flags para activación gradual de funcionalidades: -- Activar/desactivar features sin redespliegue -- Rollout gradual por porcentaje de usuarios -- Target por usuarios específicos o roles -- Condiciones personalizadas via JSONB -- Período de validez (starts_at/ends_at) -- Multi-tenant opcional - ---- - -## Características - -| Característica | Descripción | -|----------------|-------------| -| Toggle dinámico | On/off sin redespliegue | -| Rollout gradual | 0-100% con hash consistente | -| Target users | Early access para usuarios específicos | -| Target roles | Habilitar por rol (admin, user, etc.) | -| Condiciones | Reglas personalizadas vía JSONB | -| Período | Fecha inicio/fin automático | -| Multi-tenant | Flags globales o por tenant | -| Auditoría | Tracking de quién creó/modificó | - ---- - -## Stack Tecnológico - -```yaml -backend: - framework: NestJS - orm: TypeORM - database: PostgreSQL - -algoritmos: - - SHA256 hash para rollout consistente - - Determinístico por userId + featureKey -``` - ---- - -## Dependencias NPM - -```json -{ - "typeorm": "^0.3.x", - "@nestjs/typeorm": "^10.x" -} -``` - -**Nota:** No requiere librerías externas de feature flags (LaunchDarkly, Unleash, etc.) - ---- - -## Tablas Requeridas - -| Tabla | Propósito | -|-------|-----------| -| system_configuration.feature_flags | Configuración de flags | - ---- - -## Estructura del Módulo - -``` -feature-flags/ -├── entities/ -│ └── feature-flag.entity.ts -├── services/ -│ └── feature-flags.service.ts -├── controllers/ -│ └── feature-flags.controller.ts -├── dto/ -│ ├── create-feature-flag.dto.ts -│ ├── update-feature-flag.dto.ts -│ └── feature-flag-query.dto.ts -├── decorators/ -│ └── feature-flag.decorator.ts # @FeatureFlag('key') -└── guards/ - └── feature-flag.guard.ts -``` - ---- - -## Modelo de Datos - -### FeatureFlag - -```typescript -interface FeatureFlag { - id: string; // UUID - tenant_id?: string; // null = global - feature_key: string; // "new_checkout" (único) - feature_name: string; // "Nuevo Checkout v2" - description?: string; - is_enabled: boolean; // Toggle principal - rollout_percentage: number; // 0-100 - target_users?: string[]; // UUIDs con early access - target_roles?: string[]; // ['admin', 'beta_tester'] - target_conditions: Record; // { min_level: 5 } - starts_at?: Date; // Fecha inicio - ends_at?: Date; // Fecha fin - metadata: Record; // { category: 'checkout' } - created_by?: string; - updated_by?: string; - created_at: Date; - updated_at: Date; -} -``` - ---- - -## Flujo de Evaluación - -``` -isEnabled(key, userId, userRoles)? - │ - ▼ -1. Feature habilitada globalmente? - └─► NO → return false - │ - ▼ -2. Usuario en target_users? - └─► SÍ → return true (early access) - │ - ▼ -3. Usuario tiene target_role? - └─► SÍ → return true - │ - ▼ -4. rollout_percentage == 100? - └─► SÍ → return true - │ - ▼ -5. rollout_percentage == 0? - └─► SÍ → return false - │ - ▼ -6. Hash(userId + key) < rollout_percentage? - └─► SÍ → return true - └─► NO → return false -``` - ---- - -## Uso Rápido - -### 1. Verificar feature programáticamente - -```typescript -import { FeatureFlagsService } from '@/modules/feature-flags/services'; - -// En un servicio -const result = await featureFlagsService.isEnabled( - 'new_checkout', // feature key - userId, // opcional - userRoles, // opcional: ['admin', 'user'] -); - -if (result.enabled) { - // Usar nueva funcionalidad -} else { - // Usar funcionalidad legacy -} -``` - -### 2. Guard para proteger rutas - -```typescript -// Proteger endpoint completo -@Get('beta-feature') -@UseGuards(JwtAuthGuard, FeatureFlagGuard) -@FeatureFlag('beta_feature') -async betaFeature() { - return { message: 'Solo para usuarios con feature habilitada' }; -} -``` - -### 3. Decorador para check inline - -```typescript -@Get('dashboard') -async getDashboard( - @CurrentUser() user: User, - @CheckFeature('new_dashboard') hasNewDashboard: boolean, -) { - if (hasNewDashboard) { - return this.newDashboardService.getData(user.id); - } - return this.legacyDashboardService.getData(user.id); -} -``` - -### 4. En frontend (API call) - -```typescript -// GET /api/feature-flags/check/new_checkout -const response = await api.get(`/feature-flags/check/${featureKey}`, { - params: { userId }, -}); - -if (response.data.enabled) { - showNewCheckout(); -} else { - showLegacyCheckout(); -} -``` - ---- - -## Rollout Gradual - -El rollout usa un hash SHA256 del `userId + featureKey` para determinar si un usuario está en el porcentaje: - -```typescript -// Algoritmo de hash consistente -private hashUserId(userId: string, featureKey: string): number { - const hash = createHash('sha256') - .update(`${userId}:${featureKey}`) - .digest('hex'); - - const hashInt = parseInt(hash.substring(0, 8), 16); - return hashInt % 101; // 0-100 -} - -// Verificación -const userHash = hashUserId(userId, featureKey); -const isInRollout = userHash < rollout_percentage; -``` - -**Beneficios:** -- Mismo usuario siempre obtiene mismo resultado -- Distribución uniforme entre usuarios -- Diferente distribución por feature (gracias al featureKey) - ---- - -## Estrategias de Rollout - -### Canary Release - -```typescript -// 1. Crear flag deshabilitada -await featureFlagsService.create({ - key: 'new_payment_gateway', - name: 'Nuevo Gateway de Pagos', - isEnabled: false, - rolloutPercentage: 0, -}); - -// 2. Habilitar para equipo interno -await featureFlagsService.update('new_payment_gateway', { - isEnabled: true, - targetUsers: ['dev-team-uuid-1', 'dev-team-uuid-2'], -}); - -// 3. Expandir a 5% de usuarios -await featureFlagsService.updateRollout('new_payment_gateway', 5); - -// 4. Si todo OK, expandir gradualmente -await featureFlagsService.updateRollout('new_payment_gateway', 25); -await featureFlagsService.updateRollout('new_payment_gateway', 50); -await featureFlagsService.updateRollout('new_payment_gateway', 100); -``` - -### Beta por Roles - -```typescript -await featureFlagsService.create({ - key: 'advanced_analytics', - name: 'Analytics Avanzados', - isEnabled: true, - rolloutPercentage: 0, // No rollout general - targetRoles: ['admin', 'premium_user'], -}); -``` - -### Feature Temporal - -```typescript -await featureFlagsService.create({ - key: 'black_friday_banner', - name: 'Banner Black Friday', - isEnabled: true, - rolloutPercentage: 100, - startsAt: new Date('2025-11-25'), - endsAt: new Date('2025-11-30'), -}); -``` - ---- - -## Variables de Entorno - -```env -# Feature flags -FEATURE_FLAGS_CACHE_TTL=300 # Cache de 5 minutos -FEATURE_FLAGS_DEFAULT_ENABLED=false -``` - ---- - -## Endpoints Principales - -| Método | Ruta | Descripción | -|--------|------|-------------| -| GET | /admin/feature-flags | Listar todas las flags | -| GET | /admin/feature-flags/:key | Obtener flag por key | -| POST | /admin/feature-flags | Crear nueva flag | -| PUT | /admin/feature-flags/:key | Actualizar flag | -| DELETE | /admin/feature-flags/:key | Eliminar flag | -| POST | /admin/feature-flags/:key/enable | Habilitar flag | -| POST | /admin/feature-flags/:key/disable | Deshabilitar flag | -| PUT | /admin/feature-flags/:key/rollout | Actualizar porcentaje | -| POST | /feature-flags/:key/check | Verificar si está habilitada | - ---- - -## Casos de Uso Comunes - -| Caso | Configuración | -|------|---------------| -| A/B Testing | Rollout 50%, metadata con grupo | -| Beta cerrada | targetUsers con UUIDs específicos | -| Kill switch | is_enabled = false cuando hay problemas | -| Feature por plan | targetRoles: ['premium', 'enterprise'] | -| Lanzamiento programado | starts_at + ends_at | -| Migración gradual | Rollout 10% → 25% → 50% → 100% | - ---- - -## Adaptaciones Necesarias - -1. **Roles**: Ajustar enum de roles según tu sistema -2. **Cache**: Implementar cache si hay muchas verificaciones -3. **Multi-tenant**: Decidir si flags son globales o por tenant -4. **UI Admin**: Crear interfaz para gestionar flags -5. **Métricas**: Integrar con sistema de analytics - ---- - -## Referencias - -- [Feature Flags Best Practices](https://martinfowler.com/articles/feature-toggles.html) -- [Gradual Rollout Strategies](https://docs.launchdarkly.com/home/targeting-flags) - ---- - -**Mantenido por:** Sistema NEXUS -**Proyecto origen:** Gamilit Platform diff --git a/shared/libs/feature-flags/_reference/feature-flags.service.reference.ts b/shared/libs/feature-flags/_reference/feature-flags.service.reference.ts deleted file mode 100644 index 37cb9c133..000000000 --- a/shared/libs/feature-flags/_reference/feature-flags.service.reference.ts +++ /dev/null @@ -1,247 +0,0 @@ -/** - * FEATURE FLAGS SERVICE - REFERENCE IMPLEMENTATION - * - * @description Servicio de feature flags para activación/desactivación - * de funcionalidades en runtime. - * - * @usage - * ```typescript - * if (await this.featureFlags.isEnabled('new-dashboard', userId)) { - * return this.newDashboard(); - * } - * return this.legacyDashboard(); - * ``` - * - * @origin gamilit/apps/backend/src/modules/admin/services/feature-flags.service.ts - */ - -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -// Adaptar imports según proyecto -// import { FeatureFlag, FeatureFlagOverride } from '../entities'; - -/** - * Estrategias de rollout - */ -export enum RolloutStrategyEnum { - /** Todos los usuarios */ - ALL = 'all', - /** Ningún usuario */ - NONE = 'none', - /** Porcentaje de usuarios */ - PERCENTAGE = 'percentage', - /** Lista de usuarios específicos */ - USER_LIST = 'user_list', - /** Por rol */ - ROLE = 'role', - /** Por tenant */ - TENANT = 'tenant', -} - -@Injectable() -export class FeatureFlagsService { - private readonly logger = new Logger(FeatureFlagsService.name); - private cache = new Map(); - private cacheExpiry = 0; - private readonly CACHE_TTL = 60000; // 1 minuto - - constructor( - @InjectRepository(FeatureFlag, 'config') - private readonly flagRepo: Repository, - - @InjectRepository(FeatureFlagOverride, 'config') - private readonly overrideRepo: Repository, - ) {} - - /** - * Verificar si una feature está habilitada - * - * @param flagKey - Clave única del feature flag - * @param context - Contexto del usuario para evaluación - */ - async isEnabled(flagKey: string, context?: FeatureFlagContext): Promise { - // 1. Verificar override específico del usuario - if (context?.userId) { - const override = await this.getUserOverride(flagKey, context.userId); - if (override !== null) { - return override; - } - } - - // 2. Obtener flag (con cache) - const flag = await this.getFlag(flagKey); - if (!flag) { - this.logger.warn(`Feature flag not found: ${flagKey}`); - return false; - } - - // 3. Evaluar según estrategia - return this.evaluateStrategy(flag, context); - } - - /** - * Obtener todos los flags con su estado para un usuario - */ - async getAllFlags(context?: FeatureFlagContext): Promise> { - await this.refreshCacheIfNeeded(); - - const result: Record = {}; - for (const [key, flag] of this.cache) { - result[key] = await this.isEnabled(key, context); - } - - return result; - } - - /** - * Crear o actualizar un feature flag - */ - async upsertFlag(dto: UpsertFeatureFlagDto): Promise { - let flag = await this.flagRepo.findOne({ where: { key: dto.key } }); - - if (flag) { - Object.assign(flag, dto); - } else { - flag = this.flagRepo.create(dto); - } - - const saved = await this.flagRepo.save(flag); - this.invalidateCache(); - return saved; - } - - /** - * Establecer override para un usuario específico - */ - async setUserOverride( - flagKey: string, - userId: string, - enabled: boolean, - ): Promise { - await this.overrideRepo.upsert( - { flag_key: flagKey, user_id: userId, enabled }, - ['flag_key', 'user_id'], - ); - } - - /** - * Eliminar override de usuario - */ - async removeUserOverride(flagKey: string, userId: string): Promise { - await this.overrideRepo.delete({ flag_key: flagKey, user_id: userId }); - } - - // ============ HELPERS PRIVADOS ============ - - private async getFlag(key: string): Promise { - await this.refreshCacheIfNeeded(); - return this.cache.get(key) || null; - } - - private async getUserOverride(flagKey: string, userId: string): Promise { - const override = await this.overrideRepo.findOne({ - where: { flag_key: flagKey, user_id: userId }, - }); - return override?.enabled ?? null; - } - - private evaluateStrategy(flag: FeatureFlag, context?: FeatureFlagContext): boolean { - if (!flag.is_active) return false; - - switch (flag.strategy) { - case RolloutStrategyEnum.ALL: - return true; - - case RolloutStrategyEnum.NONE: - return false; - - case RolloutStrategyEnum.PERCENTAGE: - if (!context?.userId) return false; - // Hash consistente basado en userId - const hash = this.hashUserId(context.userId, flag.key); - return hash < (flag.percentage || 0); - - case RolloutStrategyEnum.USER_LIST: - if (!context?.userId) return false; - return (flag.user_list || []).includes(context.userId); - - case RolloutStrategyEnum.ROLE: - if (!context?.role) return false; - return (flag.allowed_roles || []).includes(context.role); - - case RolloutStrategyEnum.TENANT: - if (!context?.tenantId) return false; - return (flag.allowed_tenants || []).includes(context.tenantId); - - default: - return false; - } - } - - private hashUserId(userId: string, flagKey: string): number { - // Hash simple para distribución consistente - const str = `${userId}-${flagKey}`; - let hash = 0; - for (let i = 0; i < str.length; i++) { - hash = ((hash << 5) - hash) + str.charCodeAt(i); - hash |= 0; - } - return Math.abs(hash % 100); - } - - private async refreshCacheIfNeeded(): Promise { - if (Date.now() > this.cacheExpiry) { - const flags = await this.flagRepo.find(); - this.cache.clear(); - for (const flag of flags) { - this.cache.set(flag.key, flag); - } - this.cacheExpiry = Date.now() + this.CACHE_TTL; - } - } - - private invalidateCache(): void { - this.cacheExpiry = 0; - } -} - -// ============ TIPOS ============ - -interface FeatureFlagContext { - userId?: string; - role?: string; - tenantId?: string; -} - -interface FeatureFlag { - id: string; - key: string; - name: string; - description?: string; - is_active: boolean; - strategy: RolloutStrategyEnum; - percentage?: number; - user_list?: string[]; - allowed_roles?: string[]; - allowed_tenants?: string[]; -} - -interface FeatureFlagOverride { - flag_key: string; - user_id: string; - enabled: boolean; -} - -interface UpsertFeatureFlagDto { - key: string; - name: string; - description?: string; - is_active?: boolean; - strategy?: RolloutStrategyEnum; - percentage?: number; - user_list?: string[]; - allowed_roles?: string[]; - allowed_tenants?: string[]; -} diff --git a/shared/libs/multi-tenancy/IMPLEMENTATION.md b/shared/libs/multi-tenancy/IMPLEMENTATION.md deleted file mode 100644 index 2edc075f4..000000000 --- a/shared/libs/multi-tenancy/IMPLEMENTATION.md +++ /dev/null @@ -1,1054 +0,0 @@ -# Guía de Implementación: Multi-Tenancy - -**Versión:** 1.0.0 -**Tiempo estimado:** 2-4 horas -**Complejidad:** Media-Alta - ---- - -## Pre-requisitos - -- [ ] Proyecto NestJS existente -- [ ] TypeORM configurado -- [ ] PostgreSQL como base de datos -- [ ] Sistema de autenticación funcionando - ---- - -## Paso 1: Crear Estructura de Directorios - -```bash -mkdir -p src/modules/tenants/entities -mkdir -p src/modules/tenants/services -mkdir -p src/modules/tenants/controllers -mkdir -p src/modules/tenants/dto -mkdir -p src/common/middleware -mkdir -p src/common/guards -mkdir -p src/common/decorators -``` - ---- - -## Paso 2: Crear Entidad Tenant - -```typescript -// src/modules/tenants/entities/tenant.entity.ts -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - OneToMany, -} from 'typeorm'; -import { Membership } from './membership.entity'; - -export type SubscriptionTier = 'free' | 'basic' | 'pro' | 'enterprise'; - -export interface TenantSettings { - theme?: string; - features?: Record; - language?: string; - timezone?: string; -} - -@Entity({ schema: 'auth_management', name: 'tenants' }) -export class Tenant { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ length: 255 }) - name: string; - - @Column({ length: 100, unique: true }) - slug: string; - - @Column({ length: 255, nullable: true }) - domain: string; - - @Column({ name: 'logo_url', nullable: true }) - logoUrl: string; - - @Column({ - name: 'subscription_tier', - type: 'varchar', - length: 20, - default: 'free', - }) - subscriptionTier: SubscriptionTier; - - @Column({ name: 'max_users', default: 10 }) - maxUsers: number; - - @Column({ name: 'max_storage_gb', default: 1 }) - maxStorageGb: number; - - @Column({ name: 'is_active', default: true }) - isActive: boolean; - - @Column({ name: 'trial_ends_at', type: 'timestamp', nullable: true }) - trialEndsAt: Date; - - @Column({ type: 'jsonb', default: {} }) - settings: TenantSettings; - - @Column({ type: 'jsonb', default: {} }) - metadata: Record; - - @CreateDateColumn({ name: 'created_at' }) - createdAt: Date; - - @UpdateDateColumn({ name: 'updated_at' }) - updatedAt: Date; - - @OneToMany(() => Membership, (membership) => membership.tenant) - memberships: Membership[]; -} -``` - ---- - -## Paso 3: Crear Entidad Membership - -```typescript -// src/modules/tenants/entities/membership.entity.ts -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - ManyToOne, - JoinColumn, - Unique, -} from 'typeorm'; -import { Tenant } from './tenant.entity'; -import { User } from '../../auth/entities/user.entity'; - -export type MembershipRole = 'owner' | 'admin' | 'member' | 'viewer'; -export type MembershipStatus = 'pending' | 'active' | 'suspended'; - -@Entity({ schema: 'auth_management', name: 'memberships' }) -@Unique(['userId', 'tenantId']) -export class Membership { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ name: 'user_id', type: 'uuid' }) - userId: string; - - @Column({ name: 'tenant_id', type: 'uuid' }) - tenantId: string; - - @Column({ type: 'varchar', length: 20, default: 'member' }) - role: MembershipRole; - - @Column({ type: 'varchar', length: 20, default: 'pending' }) - status: MembershipStatus; - - @Column({ name: 'invited_by', type: 'uuid', nullable: true }) - invitedBy: string; - - @Column({ name: 'joined_at', type: 'timestamp', nullable: true }) - joinedAt: Date; - - @CreateDateColumn({ name: 'created_at' }) - createdAt: Date; - - @ManyToOne(() => Tenant, (tenant) => tenant.memberships) - @JoinColumn({ name: 'tenant_id' }) - tenant: Tenant; - - @ManyToOne(() => User) - @JoinColumn({ name: 'user_id' }) - user: User; -} -``` - ---- - -## Paso 4: Crear DTOs - -```typescript -// src/modules/tenants/dto/create-tenant.dto.ts -import { IsString, IsOptional, MaxLength, Matches } from 'class-validator'; - -export class CreateTenantDto { - @IsString() - @MaxLength(255) - name: string; - - @IsOptional() - @IsString() - @MaxLength(100) - @Matches(/^[a-z0-9-]+$/, { - message: 'Slug solo puede contener letras minúsculas, números y guiones', - }) - slug?: string; - - @IsOptional() - @IsString() - domain?: string; -} - -// src/modules/tenants/dto/update-tenant.dto.ts -import { PartialType } from '@nestjs/mapped-types'; -import { CreateTenantDto } from './create-tenant.dto'; -import { IsOptional, IsObject } from 'class-validator'; -import { TenantSettings } from '../entities/tenant.entity'; - -export class UpdateTenantDto extends PartialType(CreateTenantDto) { - @IsOptional() - @IsObject() - settings?: TenantSettings; -} - -// src/modules/tenants/dto/invite-member.dto.ts -import { IsEmail, IsString, IsIn } from 'class-validator'; -import { MembershipRole } from '../entities/membership.entity'; - -export class InviteMemberDto { - @IsEmail() - email: string; - - @IsString() - @IsIn(['admin', 'member', 'viewer']) - role: MembershipRole; -} - -// src/modules/tenants/dto/update-member-role.dto.ts -import { IsString, IsIn } from 'class-validator'; -import { MembershipRole } from '../entities/membership.entity'; - -export class UpdateMemberRoleDto { - @IsString() - @IsIn(['admin', 'member', 'viewer']) - role: MembershipRole; -} -``` - ---- - -## Paso 5: Crear Interfaz de Contexto - -```typescript -// src/common/interfaces/tenant-context.interface.ts -import { MembershipRole } from '../../modules/tenants/entities/membership.entity'; - -export interface TenantContext { - tenantId: string; - role: MembershipRole; -} - -// Extender Request de Express -declare global { - namespace Express { - interface Request { - tenantContext?: TenantContext; - } - } -} -``` - ---- - -## Paso 6: Crear Servicios - -### TenantService - -```typescript -// src/modules/tenants/services/tenant.service.ts -import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Tenant } from '../entities/tenant.entity'; -import { CreateTenantDto, UpdateTenantDto } from '../dto'; - -@Injectable() -export class TenantService { - constructor( - @InjectRepository(Tenant) - private readonly tenantRepository: Repository, - ) {} - - async create(dto: CreateTenantDto): Promise { - const slug = dto.slug || this.generateSlug(dto.name); - - // Verificar slug único - const existing = await this.tenantRepository.findOne({ where: { slug } }); - if (existing) { - throw new ConflictException('El slug ya está en uso'); - } - - const tenant = this.tenantRepository.create({ - ...dto, - slug, - settings: { - theme: 'default', - features: {}, - language: 'es', - timezone: 'America/Mexico_City', - }, - }); - - return this.tenantRepository.save(tenant); - } - - async findById(id: string): Promise { - const tenant = await this.tenantRepository.findOne({ where: { id } }); - if (!tenant) { - throw new NotFoundException('Tenant no encontrado'); - } - return tenant; - } - - async findBySlug(slug: string): Promise { - const tenant = await this.tenantRepository.findOne({ where: { slug } }); - if (!tenant) { - throw new NotFoundException('Tenant no encontrado'); - } - return tenant; - } - - async update(id: string, dto: UpdateTenantDto): Promise { - const tenant = await this.findById(id); - - if (dto.slug && dto.slug !== tenant.slug) { - const existing = await this.tenantRepository.findOne({ - where: { slug: dto.slug }, - }); - if (existing) { - throw new ConflictException('El slug ya está en uso'); - } - } - - Object.assign(tenant, dto); - return this.tenantRepository.save(tenant); - } - - async checkLimits(tenantId: string): Promise<{ canAddUser: boolean; currentUsers: number; maxUsers: number }> { - const tenant = await this.findById(tenantId); - const currentUsers = await this.tenantRepository - .createQueryBuilder('t') - .leftJoin('t.memberships', 'm') - .where('t.id = :tenantId', { tenantId }) - .andWhere('m.status = :status', { status: 'active' }) - .getCount(); - - return { - canAddUser: currentUsers < tenant.maxUsers, - currentUsers, - maxUsers: tenant.maxUsers, - }; - } - - private generateSlug(name: string): string { - return name - .toLowerCase() - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-|-$/g, ''); - } -} -``` - -### MembershipService - -```typescript -// src/modules/tenants/services/membership.service.ts -import { - Injectable, - NotFoundException, - ForbiddenException, - ConflictException, -} from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Membership, MembershipRole } from '../entities/membership.entity'; -import { TenantService } from './tenant.service'; - -@Injectable() -export class MembershipService { - constructor( - @InjectRepository(Membership) - private readonly membershipRepository: Repository, - private readonly tenantService: TenantService, - ) {} - - async findByUserAndTenant(userId: string, tenantId: string): Promise { - return this.membershipRepository.findOne({ - where: { userId, tenantId }, - }); - } - - async findAllByUser(userId: string): Promise { - return this.membershipRepository.find({ - where: { userId, status: 'active' }, - relations: ['tenant'], - }); - } - - async findAllByTenant(tenantId: string): Promise { - return this.membershipRepository.find({ - where: { tenantId }, - relations: ['user'], - }); - } - - async createOwnerMembership(userId: string, tenantId: string): Promise { - const membership = this.membershipRepository.create({ - userId, - tenantId, - role: 'owner', - status: 'active', - joinedAt: new Date(), - }); - return this.membershipRepository.save(membership); - } - - async inviteUser( - tenantId: string, - inviterId: string, - targetUserId: string, - role: MembershipRole, - ): Promise { - // Verificar límites - const limits = await this.tenantService.checkLimits(tenantId); - if (!limits.canAddUser) { - throw new ForbiddenException( - `Límite de usuarios alcanzado (${limits.maxUsers})`, - ); - } - - // Verificar membresía existente - const existing = await this.findByUserAndTenant(targetUserId, tenantId); - if (existing) { - throw new ConflictException('Usuario ya es miembro del tenant'); - } - - const membership = this.membershipRepository.create({ - userId: targetUserId, - tenantId, - role, - status: 'active', - invitedBy: inviterId, - joinedAt: new Date(), - }); - - return this.membershipRepository.save(membership); - } - - async updateRole( - tenantId: string, - userId: string, - newRole: MembershipRole, - ): Promise { - const membership = await this.findByUserAndTenant(userId, tenantId); - if (!membership) { - throw new NotFoundException('Membresía no encontrada'); - } - - if (membership.role === 'owner') { - throw new ForbiddenException('No se puede cambiar el rol del owner'); - } - - membership.role = newRole; - return this.membershipRepository.save(membership); - } - - async removeMember(tenantId: string, userId: string): Promise { - const membership = await this.findByUserAndTenant(userId, tenantId); - if (!membership) { - throw new NotFoundException('Membresía no encontrada'); - } - - if (membership.role === 'owner') { - throw new ForbiddenException('No se puede remover al owner'); - } - - await this.membershipRepository.remove(membership); - } - - async hasRole(userId: string, tenantId: string, roles: MembershipRole[]): Promise { - const membership = await this.findByUserAndTenant(userId, tenantId); - return membership?.status === 'active' && roles.includes(membership.role); - } -} -``` - ---- - -## Paso 7: Crear Middleware de Tenant Context - -```typescript -// src/common/middleware/tenant-context.middleware.ts -import { - Injectable, - NestMiddleware, - ForbiddenException, -} from '@nestjs/common'; -import { Request, Response, NextFunction } from 'express'; -import { MembershipService } from '../../modules/tenants/services/membership.service'; - -@Injectable() -export class TenantContextMiddleware implements NestMiddleware { - constructor(private readonly membershipService: MembershipService) {} - - async use(req: Request, res: Response, next: NextFunction) { - const tenantId = this.extractTenantId(req); - - if (!tenantId) { - return next(); // Rutas sin tenant - } - - // Verificar membresía si hay usuario autenticado - if (req.user?.id) { - const membership = await this.membershipService.findByUserAndTenant( - req.user.id, - tenantId, - ); - - if (!membership || membership.status !== 'active') { - throw new ForbiddenException('No tienes acceso a este tenant'); - } - - req.tenantContext = { - tenantId, - role: membership.role, - }; - } else { - // Usuario no autenticado pero con tenant (para rutas públicas) - req.tenantContext = { - tenantId, - role: 'viewer', - }; - } - - next(); - } - - private extractTenantId(req: Request): string | null { - // Opción 1: Header X-Tenant-ID - const headerTenant = req.headers['x-tenant-id'] as string; - if (headerTenant) return headerTenant; - - // Opción 2: Subdomain - const host = req.hostname || req.headers.host || ''; - const parts = host.split('.'); - if (parts.length >= 3) { - const subdomain = parts[0]; - if (subdomain !== 'www' && subdomain !== 'app' && subdomain !== 'api') { - return subdomain; // Este sería el slug, convertir a ID si es necesario - } - } - - // Opción 3: Query param - const queryTenant = req.query.tenant as string; - if (queryTenant) return queryTenant; - - return null; - } -} -``` - ---- - -## Paso 8: Crear Guards - -```typescript -// src/common/guards/tenant-member.guard.ts -import { - Injectable, - CanActivate, - ExecutionContext, - ForbiddenException, -} from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { MembershipRole } from '../../modules/tenants/entities/membership.entity'; - -export const TENANT_ROLES_KEY = 'tenant_roles'; - -@Injectable() -export class TenantMemberGuard implements CanActivate { - constructor(private reflector: Reflector) {} - - canActivate(context: ExecutionContext): boolean { - const request = context.switchToHttp().getRequest(); - - if (!request.tenantContext) { - throw new ForbiddenException('Tenant context requerido'); - } - - // Verificar roles si están definidos - const requiredRoles = this.reflector.getAllAndOverride( - TENANT_ROLES_KEY, - [context.getHandler(), context.getClass()], - ); - - if (!requiredRoles || requiredRoles.length === 0) { - return true; // Solo requiere membresía activa - } - - if (!requiredRoles.includes(request.tenantContext.role)) { - throw new ForbiddenException( - `Rol requerido: ${requiredRoles.join(' o ')}`, - ); - } - - return true; - } -} - -// src/common/guards/tenant-admin.guard.ts -import { - Injectable, - CanActivate, - ExecutionContext, - ForbiddenException, -} from '@nestjs/common'; - -@Injectable() -export class TenantAdminGuard implements CanActivate { - canActivate(context: ExecutionContext): boolean { - const request = context.switchToHttp().getRequest(); - - if (!request.tenantContext) { - throw new ForbiddenException('Tenant context requerido'); - } - - const allowedRoles = ['owner', 'admin']; - if (!allowedRoles.includes(request.tenantContext.role)) { - throw new ForbiddenException('Se requiere rol de administrador'); - } - - return true; - } -} - -// src/common/guards/tenant-owner.guard.ts -import { - Injectable, - CanActivate, - ExecutionContext, - ForbiddenException, -} from '@nestjs/common'; - -@Injectable() -export class TenantOwnerGuard implements CanActivate { - canActivate(context: ExecutionContext): boolean { - const request = context.switchToHttp().getRequest(); - - if (!request.tenantContext) { - throw new ForbiddenException('Tenant context requerido'); - } - - if (request.tenantContext.role !== 'owner') { - throw new ForbiddenException('Se requiere rol de owner'); - } - - return true; - } -} -``` - ---- - -## Paso 9: Crear Decoradores - -```typescript -// src/common/decorators/current-tenant.decorator.ts -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; -import { TenantContext } from '../interfaces/tenant-context.interface'; - -export const CurrentTenant = createParamDecorator( - (data: keyof TenantContext | undefined, ctx: ExecutionContext) => { - const request = ctx.switchToHttp().getRequest(); - const tenantContext = request.tenantContext; - - if (!tenantContext) { - return null; - } - - return data ? tenantContext[data] : tenantContext; - }, -); - -// src/common/decorators/tenant-roles.decorator.ts -import { SetMetadata } from '@nestjs/common'; -import { MembershipRole } from '../../modules/tenants/entities/membership.entity'; -import { TENANT_ROLES_KEY } from '../guards/tenant-member.guard'; - -export const TenantRoles = (...roles: MembershipRole[]) => - SetMetadata(TENANT_ROLES_KEY, roles); -``` - ---- - -## Paso 10: Crear Controller - -```typescript -// src/modules/tenants/controllers/tenant.controller.ts -import { - Controller, - Get, - Post, - Put, - Delete, - Body, - Param, - UseGuards, - ParseUUIDPipe, -} from '@nestjs/common'; -import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; -import { TenantMemberGuard, TenantAdminGuard, TenantOwnerGuard } from '../../../common/guards'; -import { CurrentTenant, TenantRoles } from '../../../common/decorators'; -import { CurrentUser } from '../../auth/decorators/current-user.decorator'; -import { TenantService } from '../services/tenant.service'; -import { MembershipService } from '../services/membership.service'; -import { CreateTenantDto, UpdateTenantDto, InviteMemberDto, UpdateMemberRoleDto } from '../dto'; -import { TenantContext } from '../../../common/interfaces/tenant-context.interface'; - -@Controller('tenants') -@UseGuards(JwtAuthGuard) -export class TenantController { - constructor( - private readonly tenantService: TenantService, - private readonly membershipService: MembershipService, - ) {} - - // Listar tenants del usuario actual - @Get() - async getMyTenants(@CurrentUser('id') userId: string) { - const memberships = await this.membershipService.findAllByUser(userId); - return memberships.map((m) => ({ - ...m.tenant, - role: m.role, - })); - } - - // Crear nuevo tenant (usuario se convierte en owner) - @Post() - async create( - @CurrentUser('id') userId: string, - @Body() dto: CreateTenantDto, - ) { - const tenant = await this.tenantService.create(dto); - await this.membershipService.createOwnerMembership(userId, tenant.id); - return tenant; - } - - // Obtener detalle de tenant (requiere membresía) - @Get(':id') - @UseGuards(TenantMemberGuard) - async getOne( - @Param('id', ParseUUIDPipe) id: string, - @CurrentTenant() tenant: TenantContext, - ) { - return this.tenantService.findById(id); - } - - // Actualizar tenant (solo admin+) - @Put(':id') - @UseGuards(TenantAdminGuard) - async update( - @Param('id', ParseUUIDPipe) id: string, - @Body() dto: UpdateTenantDto, - ) { - return this.tenantService.update(id, dto); - } - - // Listar miembros del tenant - @Get(':id/members') - @UseGuards(TenantMemberGuard) - async getMembers(@Param('id', ParseUUIDPipe) tenantId: string) { - return this.membershipService.findAllByTenant(tenantId); - } - - // Invitar usuario (solo admin+) - @Post(':id/invite') - @UseGuards(TenantAdminGuard) - async inviteMember( - @Param('id', ParseUUIDPipe) tenantId: string, - @CurrentUser('id') inviterId: string, - @Body() dto: InviteMemberDto, - ) { - // Aquí deberías buscar el usuario por email y obtener su ID - // Por simplicidad, asumimos que tienes un UserService - // const user = await this.userService.findByEmail(dto.email); - // return this.membershipService.inviteUser(tenantId, inviterId, user.id, dto.role); - } - - // Cambiar rol de miembro (solo owner) - @Put(':id/members/:userId/role') - @UseGuards(TenantOwnerGuard) - async updateMemberRole( - @Param('id', ParseUUIDPipe) tenantId: string, - @Param('userId', ParseUUIDPipe) userId: string, - @Body() dto: UpdateMemberRoleDto, - ) { - return this.membershipService.updateRole(tenantId, userId, dto.role); - } - - // Remover miembro (solo admin+) - @Delete(':id/members/:userId') - @UseGuards(TenantAdminGuard) - async removeMember( - @Param('id', ParseUUIDPipe) tenantId: string, - @Param('userId', ParseUUIDPipe) userId: string, - ) { - await this.membershipService.removeMember(tenantId, userId); - return { message: 'Miembro removido exitosamente' }; - } -} -``` - ---- - -## Paso 11: Crear Módulo - -```typescript -// src/modules/tenants/tenants.module.ts -import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Tenant } from './entities/tenant.entity'; -import { Membership } from './entities/membership.entity'; -import { TenantService } from './services/tenant.service'; -import { MembershipService } from './services/membership.service'; -import { TenantController } from './controllers/tenant.controller'; -import { TenantContextMiddleware } from '../../common/middleware/tenant-context.middleware'; - -@Module({ - imports: [TypeOrmModule.forFeature([Tenant, Membership])], - controllers: [TenantController], - providers: [TenantService, MembershipService], - exports: [TenantService, MembershipService], -}) -export class TenantsModule { - configure(consumer: MiddlewareConsumer) { - consumer - .apply(TenantContextMiddleware) - .forRoutes({ path: '*', method: RequestMethod.ALL }); - } -} -``` - ---- - -## Paso 12: Registrar en AppModule - -```typescript -// src/app.module.ts -import { Module } from '@nestjs/common'; -import { TenantsModule } from './modules/tenants/tenants.module'; - -@Module({ - imports: [ - // ... otros módulos - TenantsModule, - ], -}) -export class AppModule {} -``` - ---- - -## Paso 13: Migraciones SQL - -```sql --- migrations/001_create_tenants.sql -CREATE SCHEMA IF NOT EXISTS auth_management; - --- Tabla de tenants -CREATE TABLE auth_management.tenants ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(255) NOT NULL, - slug VARCHAR(100) UNIQUE NOT NULL, - domain VARCHAR(255), - logo_url TEXT, - subscription_tier VARCHAR(20) DEFAULT 'free', - max_users INTEGER DEFAULT 10, - max_storage_gb INTEGER DEFAULT 1, - is_active BOOLEAN DEFAULT true, - trial_ends_at TIMESTAMP, - settings JSONB DEFAULT '{}', - metadata JSONB DEFAULT '{}', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Tabla de memberships -CREATE TABLE auth_management.memberships ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES auth_management.users(id) ON DELETE CASCADE, - tenant_id UUID NOT NULL REFERENCES auth_management.tenants(id) ON DELETE CASCADE, - role VARCHAR(20) DEFAULT 'member', - status VARCHAR(20) DEFAULT 'pending', - invited_by UUID REFERENCES auth_management.users(id), - joined_at TIMESTAMP, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(user_id, tenant_id) -); - --- Índices -CREATE INDEX idx_tenants_slug ON auth_management.tenants(slug); -CREATE INDEX idx_memberships_user ON auth_management.memberships(user_id); -CREATE INDEX idx_memberships_tenant ON auth_management.memberships(tenant_id); -CREATE INDEX idx_memberships_status ON auth_management.memberships(status); -``` - ---- - -## Paso 14: Row Level Security (Opcional) - -Para seguridad adicional a nivel de base de datos: - -```sql --- Habilitar RLS en tablas con datos por tenant -ALTER TABLE projects ENABLE ROW LEVEL SECURITY; - --- Crear policy de aislamiento -CREATE POLICY tenant_isolation ON projects - USING ( - tenant_id IN ( - SELECT tenant_id FROM auth_management.memberships - WHERE user_id = current_setting('app.current_user_id')::uuid - AND status = 'active' - ) - ); - --- En el middleware o interceptor, setear el user_id antes de cada query: --- SET app.current_user_id = 'user-uuid'; -``` - ---- - -## Paso 15: Uso en Otros Servicios - -```typescript -// Ejemplo: ProjectService con filtro por tenant -@Injectable() -export class ProjectService { - constructor( - @InjectRepository(Project) - private readonly projectRepository: Repository, - ) {} - - async findAll(tenantId: string): Promise { - return this.projectRepository.find({ - where: { tenant_id: tenantId }, - }); - } - - async create(tenantId: string, dto: CreateProjectDto): Promise { - const project = this.projectRepository.create({ - ...dto, - tenant_id: tenantId, // SIEMPRE asignar tenant - }); - return this.projectRepository.save(project); - } - - async findOne(tenantId: string, projectId: string): Promise { - const project = await this.projectRepository.findOne({ - where: { id: projectId, tenant_id: tenantId }, // SIEMPRE filtrar por tenant - }); - if (!project) { - throw new NotFoundException('Proyecto no encontrado'); - } - return project; - } -} - -// En el controller -@Get('projects') -@UseGuards(JwtAuthGuard, TenantMemberGuard) -async getProjects(@CurrentTenant('tenantId') tenantId: string) { - return this.projectService.findAll(tenantId); -} -``` - ---- - -## Variables de Entorno - -```env -# Multi-tenancy -ENABLE_MULTITENANCY=true -DEFAULT_TENANT_SLUG=main - -# Límites por defecto para nuevos tenants -DEFAULT_MAX_USERS=10 -DEFAULT_MAX_STORAGE_GB=1 - -# Tiers de suscripción (JSON para configuración) -SUBSCRIPTION_TIERS={"free":{"maxUsers":10,"maxStorage":1},"basic":{"maxUsers":50,"maxStorage":10},"pro":{"maxUsers":200,"maxStorage":50},"enterprise":{"maxUsers":-1,"maxStorage":-1}} -``` - ---- - -## Checklist de Implementación - -- [ ] Entidades Tenant y Membership creadas -- [ ] DTOs de validación creados -- [ ] TenantService implementado -- [ ] MembershipService implementado -- [ ] TenantContextMiddleware configurado -- [ ] Guards (TenantMember, TenantAdmin, TenantOwner) creados -- [ ] Decoradores (CurrentTenant, TenantRoles) creados -- [ ] TenantController con endpoints básicos -- [ ] TenantsModule registrado en AppModule -- [ ] Migraciones SQL ejecutadas -- [ ] Variables de entorno configuradas -- [ ] Build pasa sin errores -- [ ] Tests de integración pasan - ---- - -## Verificar Funcionamiento - -```bash -# 1. Crear tenant -curl -X POST http://localhost:3000/api/tenants \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name": "Mi Empresa"}' - -# 2. Listar mis tenants -curl http://localhost:3000/api/tenants \ - -H "Authorization: Bearer $TOKEN" - -# 3. Acceder con header X-Tenant-ID -curl http://localhost:3000/api/projects \ - -H "Authorization: Bearer $TOKEN" \ - -H "X-Tenant-ID: tenant-uuid" - -# 4. Verificar acceso denegado a otro tenant -curl http://localhost:3000/api/projects \ - -H "Authorization: Bearer $TOKEN" \ - -H "X-Tenant-ID: otro-tenant-uuid" -# Debería retornar 403 Forbidden -``` - ---- - -## Troubleshooting - -### Error: "No tienes acceso a este tenant" -- Verificar que el usuario tenga membresía activa en el tenant -- Verificar que el tenant_id en el header sea correcto (UUID o slug) - -### Error: "Tenant context requerido" -- El guard TenantMemberGuard requiere header X-Tenant-ID -- Verificar que el middleware esté configurado correctamente - -### Los datos se mezclan entre tenants -- Verificar que TODOS los queries filtren por tenant_id -- Considerar implementar RLS para seguridad adicional - -### Límite de usuarios no funciona -- Verificar que checkLimits() se llame antes de crear membresías -- Verificar configuración de maxUsers en el tenant - ---- - -**Versión:** 1.0.0 -**Sistema:** SIMCO Catálogo diff --git a/shared/libs/multi-tenancy/README.md b/shared/libs/multi-tenancy/README.md deleted file mode 100644 index 8852e853a..000000000 --- a/shared/libs/multi-tenancy/README.md +++ /dev/null @@ -1,434 +0,0 @@ -# Soporte Multi-Tenant - -**Versión:** 1.0.0 -**Origen:** projects/gamilit -**Estado:** Producción -**Última actualización:** 2025-12-08 - ---- - -## Descripción - -Sistema de multi-tenancy para aplicaciones SaaS: -- Aislamiento de datos por organización -- Usuarios pueden pertenecer a múltiples tenants -- Roles específicos por tenant (owner, admin, member) -- Configuración y personalización por tenant -- Límites de recursos (usuarios, storage) -- Niveles de suscripción - ---- - -## Características - -| Característica | Descripción | -|----------------|-------------| -| Aislamiento | Datos separados por tenant | -| Multi-membresía | Usuario en múltiples tenants | -| Roles por tenant | owner, admin, member, viewer | -| Suscripciones | free, basic, pro, enterprise | -| Personalización | Theme, logo, dominio custom | -| Límites | Máx usuarios, storage | -| RLS (opcional) | Row Level Security en PostgreSQL | - ---- - -## Stack Tecnológico - -```yaml -backend: - framework: NestJS - orm: TypeORM - database: PostgreSQL - -patterns: - - Tenant discriminator (tenant_id en tablas) - - Middleware de tenant context - - Optional: RLS para seguridad extra -``` - ---- - -## Tablas Requeridas - -| Tabla | Propósito | -|-------|-----------| -| auth_management.tenants | Organizaciones/empresas | -| auth_management.memberships | Relación usuario-tenant | -| auth_management.profiles | Perfil extendido con tenant_id | - ---- - -## Estructura del Módulo - -``` -multi-tenancy/ -├── entities/ -│ ├── tenant.entity.ts -│ └── membership.entity.ts -├── services/ -│ ├── tenant.service.ts -│ └── membership.service.ts -├── middleware/ -│ └── tenant-context.middleware.ts -├── guards/ -│ └── tenant-member.guard.ts -├── decorators/ -│ └── current-tenant.decorator.ts -└── dto/ - ├── create-tenant.dto.ts - └── membership.dto.ts -``` - ---- - -## Modelos de Datos - -### Tenant (Organización) - -```typescript -interface Tenant { - id: string; // UUID - name: string; // "Empresa XYZ" - slug: string; // "empresa-xyz" (único) - domain?: string; // "xyz.app.com" - logo_url?: string; - subscription_tier: 'free' | 'basic' | 'pro' | 'enterprise'; - max_users: number; // Límite de usuarios - max_storage_gb: number; // Límite de storage - is_active: boolean; - trial_ends_at?: Date; - settings: { // Configuración JSONB - theme: string; - features: Record; - language: string; - timezone: string; - }; - metadata: Record; -} -``` - -### Membership (Usuario-Tenant) - -```typescript -interface Membership { - id: string; - user_id: string; - tenant_id: string; - role: 'owner' | 'admin' | 'member' | 'viewer'; - status: 'pending' | 'active' | 'suspended'; - invited_by?: string; - joined_at: Date; -} -``` - ---- - -## Flujo de Multi-Tenancy - -``` -1. Usuario autenticado - │ - ▼ -2. Middleware extrae tenant de: - - Header: X-Tenant-ID - - Subdomain: xyz.app.com → "xyz" - - Query param: ?tenant=xyz - │ - ▼ -3. Verificar membresía activa - │ - ▼ -4. Inyectar tenant_id en contexto - │ - ▼ -5. Queries filtran por tenant_id -``` - ---- - -## Uso Rápido - -### 1. Middleware de Tenant - -```typescript -// src/common/middleware/tenant-context.middleware.ts -@Injectable() -export class TenantContextMiddleware implements NestMiddleware { - constructor( - private readonly membershipService: MembershipService, - ) {} - - async use(req: Request, res: Response, next: NextFunction) { - const tenantId = this.extractTenantId(req); - - if (!tenantId) { - return next(); // Rutas sin tenant - } - - // Verificar membresía si hay usuario - if (req.user?.id) { - const membership = await this.membershipService.findByUserAndTenant( - req.user.id, - tenantId, - ); - - if (!membership || membership.status !== 'active') { - throw new ForbiddenException('No tienes acceso a este tenant'); - } - - req.tenantContext = { - tenantId, - role: membership.role, - }; - } - - next(); - } - - private extractTenantId(req: Request): string | null { - // Opción 1: Header - const headerTenant = req.headers['x-tenant-id'] as string; - if (headerTenant) return headerTenant; - - // Opción 2: Subdomain - const host = req.hostname; - const subdomain = host.split('.')[0]; - if (subdomain && subdomain !== 'www' && subdomain !== 'app') { - return subdomain; - } - - // Opción 3: Query param - return req.query.tenant as string; - } -} -``` - -### 2. Guard de Membresía - -```typescript -// src/common/guards/tenant-member.guard.ts -@Injectable() -export class TenantMemberGuard implements CanActivate { - canActivate(context: ExecutionContext): boolean { - const req = context.switchToHttp().getRequest(); - - if (!req.tenantContext) { - throw new ForbiddenException('Tenant context required'); - } - - return true; - } -} - -// Guard con rol específico -@Injectable() -export class TenantAdminGuard implements CanActivate { - canActivate(context: ExecutionContext): boolean { - const req = context.switchToHttp().getRequest(); - - if (!req.tenantContext) { - throw new ForbiddenException('Tenant context required'); - } - - const allowedRoles = ['owner', 'admin']; - if (!allowedRoles.includes(req.tenantContext.role)) { - throw new ForbiddenException('Admin role required'); - } - - return true; - } -} -``` - -### 3. Decorador para obtener tenant - -```typescript -// src/common/decorators/current-tenant.decorator.ts -export const CurrentTenant = createParamDecorator( - (data: unknown, ctx: ExecutionContext) => { - const request = ctx.switchToHttp().getRequest(); - return request.tenantContext; - }, -); - -// Uso en controller -@Get('data') -@UseGuards(JwtAuthGuard, TenantMemberGuard) -getData(@CurrentTenant() tenant: TenantContext) { - return this.service.findByTenant(tenant.tenantId); -} -``` - -### 4. Service con filtro de tenant - -```typescript -@Injectable() -export class ProjectService { - async findAll(tenantId: string): Promise { - return this.projectRepository.find({ - where: { tenant_id: tenantId }, - }); - } - - async create(tenantId: string, dto: CreateProjectDto): Promise { - const project = this.projectRepository.create({ - ...dto, - tenant_id: tenantId, // Siempre asignar tenant - }); - return this.projectRepository.save(project); - } -} -``` - ---- - -## Crear Nuevo Tenant - -```typescript -async createTenant(dto: CreateTenantDto, ownerId: string): Promise { - // 1. Crear tenant - const tenant = this.tenantRepository.create({ - name: dto.name, - slug: this.generateSlug(dto.name), - subscription_tier: 'free', - max_users: 10, - max_storage_gb: 1, - settings: { - theme: 'default', - features: { analytics: true }, - language: 'es', - }, - }); - await this.tenantRepository.save(tenant); - - // 2. Crear membresía de owner - const membership = this.membershipRepository.create({ - user_id: ownerId, - tenant_id: tenant.id, - role: 'owner', - status: 'active', - joined_at: new Date(), - }); - await this.membershipRepository.save(membership); - - return tenant; -} -``` - ---- - -## Invitar Usuario a Tenant - -```typescript -async inviteUser( - tenantId: string, - inviterId: string, - email: string, - role: string, -): Promise { - // 1. Buscar usuario por email - const user = await this.userRepository.findOne({ where: { email } }); - - if (!user) { - // Enviar invitación por email para registro - await this.mailService.sendInvitation(email, tenantId, role); - return; - } - - // 2. Verificar si ya es miembro - const existing = await this.membershipRepository.findOne({ - where: { user_id: user.id, tenant_id: tenantId }, - }); - - if (existing) { - throw new ConflictException('Usuario ya es miembro'); - } - - // 3. Crear membresía - const membership = this.membershipRepository.create({ - user_id: user.id, - tenant_id: tenantId, - role, - status: 'active', - invited_by: inviterId, - joined_at: new Date(), - }); - await this.membershipRepository.save(membership); -} -``` - ---- - -## Row Level Security (Opcional) - -Para seguridad adicional a nivel de base de datos: - -```sql --- Habilitar RLS en tabla -ALTER TABLE projects ENABLE ROW LEVEL SECURITY; - --- Policy: usuarios solo ven proyectos de sus tenants -CREATE POLICY tenant_isolation ON projects - USING ( - tenant_id IN ( - SELECT tenant_id FROM memberships - WHERE user_id = current_setting('app.current_user_id')::uuid - AND status = 'active' - ) - ); - --- Antes de cada query, setear el user_id -SET app.current_user_id = 'user-uuid'; -``` - ---- - -## Variables de Entorno - -```env -# Multi-tenancy -ENABLE_MULTITENANCY=true -DEFAULT_TENANT_SLUG=main - -# Límites por defecto -DEFAULT_MAX_USERS=100 -DEFAULT_MAX_STORAGE_GB=5 -``` - ---- - -## Endpoints Principales - -| Método | Ruta | Descripción | -|--------|------|-------------| -| GET | /tenants | Listar tenants del usuario | -| POST | /tenants | Crear nuevo tenant | -| GET | /tenants/:id | Obtener detalle de tenant | -| PUT | /tenants/:id | Actualizar tenant (admin) | -| POST | /tenants/:id/invite | Invitar usuario | -| GET | /tenants/:id/members | Listar miembros | -| PUT | /tenants/:id/members/:userId | Cambiar rol | -| DELETE | /tenants/:id/members/:userId | Remover miembro | - ---- - -## Adaptaciones Necesarias - -1. **Método de detección**: Header, subdomain, o query param -2. **Roles**: Ajustar según necesidades (owner, admin, etc.) -3. **Suscripciones**: Definir tiers y límites -4. **Settings**: Estructura de configuración por tenant -5. **RLS**: Implementar si se requiere seguridad extra - ---- - -## Referencias - -- [NestJS Multi-Tenancy Patterns](https://docs.nestjs.com/) -- [PostgreSQL Row Level Security](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) - ---- - -**Mantenido por:** Sistema NEXUS -**Proyecto origen:** Gamilit Platform diff --git a/shared/libs/multi-tenancy/_reference/tenant.guard.reference.ts b/shared/libs/multi-tenancy/_reference/tenant.guard.reference.ts deleted file mode 100644 index 1cf6dd336..000000000 --- a/shared/libs/multi-tenancy/_reference/tenant.guard.reference.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * TENANT GUARD - REFERENCE IMPLEMENTATION - * - * @description Guard para validación de multi-tenancy. - * Asegura que el usuario pertenece al tenant correcto. - * - * @usage - * ```typescript - * @UseGuards(JwtAuthGuard, TenantGuard) - * @Get('data') - * getData(@CurrentTenant() tenant: Tenant) { ... } - * ``` - * - * @origin gamilit/apps/backend/src/shared/guards/tenant.guard.ts - */ - -import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -// Adaptar imports según proyecto -// import { Tenant } from '../entities'; - -/** - * Metadata key para configuración de tenant - */ -export const TENANT_KEY = 'tenant'; -export const SKIP_TENANT_KEY = 'skipTenant'; - -@Injectable() -export class TenantGuard implements CanActivate { - constructor( - private readonly reflector: Reflector, - @InjectRepository(Tenant, 'auth') - private readonly tenantRepository: Repository, - ) {} - - async canActivate(context: ExecutionContext): Promise { - // Verificar si la ruta debe saltarse la validación de tenant - const skipTenant = this.reflector.getAllAndOverride(SKIP_TENANT_KEY, [ - context.getHandler(), - context.getClass(), - ]); - - if (skipTenant) { - return true; - } - - const request = context.switchToHttp().getRequest(); - const user = request.user; - - if (!user) { - throw new ForbiddenException('Usuario no autenticado'); - } - - // Obtener tenant del usuario (asumiendo que está en el JWT o perfil) - const tenantId = user.tenant_id || request.headers['x-tenant-id']; - - if (!tenantId) { - throw new ForbiddenException('Tenant no especificado'); - } - - // Validar que el tenant existe y está activo - const tenant = await this.tenantRepository.findOne({ - where: { id: tenantId, is_active: true }, - }); - - if (!tenant) { - throw new ForbiddenException('Tenant inválido o inactivo'); - } - - // Inyectar tenant en request para uso posterior - request.tenant = tenant; - - return true; - } -} - -// ============ DECORADORES ============ - -import { SetMetadata, createParamDecorator } from '@nestjs/common'; - -/** - * Decorador para saltar validación de tenant - */ -export const SkipTenant = () => SetMetadata(SKIP_TENANT_KEY, true); - -/** - * Decorador para obtener el tenant actual - */ -export const CurrentTenant = createParamDecorator( - (data: unknown, ctx: ExecutionContext) => { - const request = ctx.switchToHttp().getRequest(); - return request.tenant; - }, -); - -// ============ RLS HELPER ============ - -/** - * Helper para aplicar Row-Level Security en queries - * - * @usage - * ```typescript - * const users = await this.userRepo - * .createQueryBuilder('user') - * .where(withTenant('user', tenantId)) - * .getMany(); - * ``` - */ -export function withTenant(alias: string, tenantId: string): string { - return `${alias}.tenant_id = '${tenantId}'`; -} - -/** - * Interceptor para inyectar tenant_id automáticamente en creates - * - * @usage En el servicio base - * ```typescript - * async create(dto: CreateDto, tenantId: string) { - * const entity = this.repo.create({ - * ...dto, - * tenant_id: tenantId, - * }); - * return this.repo.save(entity); - * } - * ``` - */ -export function injectTenantId( - entity: T, - tenantId: string, -): T { - return { ...entity, tenant_id: tenantId }; -} - -// ============ TIPOS ============ - -interface Tenant { - id: string; - name: string; - slug: string; - is_active: boolean; -} diff --git a/shared/libs/notifications/IMPLEMENTATION.md b/shared/libs/notifications/IMPLEMENTATION.md deleted file mode 100644 index 079456911..000000000 --- a/shared/libs/notifications/IMPLEMENTATION.md +++ /dev/null @@ -1,642 +0,0 @@ -# Guía de Implementación: Sistema de Notificaciones - -**Versión:** 1.0.0 -**Tiempo estimado:** 4-8 horas -**Complejidad:** Alta - ---- - -## Pre-requisitos - -- [ ] NestJS con TypeORM configurado -- [ ] PostgreSQL -- [ ] Cuenta SMTP o SendGrid (para email) -- [ ] Claves VAPID (para push) - ---- - -## Paso 1: Instalar Dependencias - -```bash -# Core -npm install nodemailer -npm install -D @types/nodemailer - -# Push notifications -npm install web-push -npm install -D @types/web-push - -# TypeORM (si no está instalado) -npm install typeorm @nestjs/typeorm -``` - ---- - -## Paso 2: Crear DDL - -### 2.1 Schema y tabla principal - -```sql -CREATE SCHEMA IF NOT EXISTS notifications; - --- Tabla principal de notificaciones -CREATE TABLE notifications.notifications ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - type VARCHAR(50) NOT NULL, - title VARCHAR(255) NOT NULL, - message TEXT NOT NULL, - data JSONB DEFAULT '{}', - priority VARCHAR(20) DEFAULT 'normal' CHECK (priority IN ('low', 'normal', 'high', 'urgent')), - channels VARCHAR(20)[] DEFAULT ARRAY['in_app'], - status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'sent', 'read', 'failed')), - read_at TIMESTAMPTZ, - sent_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW(), - expires_at TIMESTAMPTZ, - metadata JSONB DEFAULT '{}' -); - -CREATE INDEX idx_notifications_user_id ON notifications.notifications(user_id); -CREATE INDEX idx_notifications_type ON notifications.notifications(type); -CREATE INDEX idx_notifications_status ON notifications.notifications(status); -CREATE INDEX idx_notifications_created_at ON notifications.notifications(created_at); -``` - -### 2.2 Preferencias - -```sql -CREATE TABLE notifications.notification_preferences ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - notification_type VARCHAR(50) NOT NULL, - in_app_enabled BOOLEAN DEFAULT true, - email_enabled BOOLEAN DEFAULT true, - push_enabled BOOLEAN DEFAULT false, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(user_id, notification_type) -); - -CREATE INDEX idx_notification_preferences_user ON notifications.notification_preferences(user_id); -``` - -### 2.3 Cola de procesamiento - -```sql -CREATE TABLE notifications.notification_queue ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - notification_id UUID NOT NULL REFERENCES notifications.notifications(id) ON DELETE CASCADE, - channel VARCHAR(20) NOT NULL CHECK (channel IN ('in_app', 'email', 'push')), - status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed')), - attempts INTEGER DEFAULT 0, - max_attempts INTEGER DEFAULT 3, - last_attempt_at TIMESTAMPTZ, - next_attempt_at TIMESTAMPTZ DEFAULT NOW(), - error_message TEXT, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_notification_queue_status ON notifications.notification_queue(status); -CREATE INDEX idx_notification_queue_next_attempt ON notifications.notification_queue(next_attempt_at); -``` - -### 2.4 Dispositivos para push - -```sql -CREATE TABLE notifications.user_devices ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - device_type VARCHAR(20) NOT NULL CHECK (device_type IN ('web', 'ios', 'android')), - subscription JSONB NOT NULL, -- Web Push subscription object - browser VARCHAR(100), - os VARCHAR(100), - is_active BOOLEAN DEFAULT true, - created_at TIMESTAMPTZ DEFAULT NOW(), - last_used_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE INDEX idx_user_devices_user ON notifications.user_devices(user_id); -CREATE INDEX idx_user_devices_active ON notifications.user_devices(is_active); -``` - ---- - -## Paso 3: Crear Entities - -### 3.1 Notification Entity - -```typescript -// src/modules/notifications/entities/notification.entity.ts -import { - Entity, - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - Index, -} from 'typeorm'; - -@Entity({ schema: 'notifications', name: 'notifications' }) -@Index(['userId']) -@Index(['type']) -@Index(['status']) -export class Notification { - @PrimaryGeneratedColumn('uuid') - id!: string; - - @Column({ name: 'user_id', type: 'uuid' }) - userId!: string; - - @Column({ type: 'varchar', length: 50 }) - type!: string; - - @Column({ type: 'varchar', length: 255 }) - title!: string; - - @Column({ type: 'text' }) - message!: string; - - @Column({ type: 'jsonb', default: {} }) - data?: Record; - - @Column({ type: 'varchar', length: 20, default: 'normal' }) - priority!: string; - - @Column({ type: 'varchar', array: true, default: ['in_app'] }) - channels!: string[]; - - @Column({ type: 'varchar', length: 20, default: 'pending' }) - status!: string; - - @Column({ name: 'read_at', type: 'timestamp', nullable: true }) - readAt?: Date; - - @Column({ name: 'sent_at', type: 'timestamp', nullable: true }) - sentAt?: Date; - - @CreateDateColumn({ name: 'created_at' }) - createdAt!: Date; - - @Column({ name: 'expires_at', type: 'timestamp', nullable: true }) - expiresAt?: Date; - - @Column({ type: 'jsonb', default: {} }) - metadata?: Record; -} -``` - -### 3.2 NotificationPreference Entity - -```typescript -// src/modules/notifications/entities/notification-preference.entity.ts -import { - Entity, - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, - Index, -} from 'typeorm'; - -@Entity({ schema: 'notifications', name: 'notification_preferences' }) -@Index(['userId', 'notificationType'], { unique: true }) -export class NotificationPreference { - @PrimaryGeneratedColumn('uuid') - id!: string; - - @Column({ name: 'user_id', type: 'uuid' }) - userId!: string; - - @Column({ name: 'notification_type', type: 'varchar', length: 50 }) - notificationType!: string; - - @Column({ name: 'in_app_enabled', type: 'boolean', default: true }) - inAppEnabled!: boolean; - - @Column({ name: 'email_enabled', type: 'boolean', default: true }) - emailEnabled!: boolean; - - @Column({ name: 'push_enabled', type: 'boolean', default: false }) - pushEnabled!: boolean; - - @CreateDateColumn({ name: 'created_at' }) - createdAt!: Date; - - @UpdateDateColumn({ name: 'updated_at' }) - updatedAt!: Date; -} -``` - ---- - -## Paso 4: Crear NotificationService - -```typescript -// src/modules/notifications/services/notification.service.ts -import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Notification } from '../entities/notification.entity'; - -@Injectable() -export class NotificationService { - constructor( - @InjectRepository(Notification) - private readonly notificationRepository: Repository, - ) {} - - async create(data: { - userId: string; - title: string; - message: string; - type: string; - data?: Record; - priority?: string; - channels?: string[]; - expiresAt?: Date; - }): Promise { - const notification = this.notificationRepository.create({ - userId: data.userId, - title: data.title, - message: data.message, - type: data.type, - data: data.data, - priority: data.priority || 'normal', - channels: data.channels || ['in_app'], - status: 'sent', - sentAt: new Date(), - expiresAt: data.expiresAt, - }); - - return this.notificationRepository.save(notification); - } - - async findAllByUser( - userId: string, - filters?: { - status?: string; - type?: string; - limit?: number; - offset?: number; - }, - ): Promise<{ data: Notification[]; total: number }> { - const query = this.notificationRepository - .createQueryBuilder('n') - .where('n.user_id = :userId', { userId }); - - if (filters?.status) { - query.andWhere('n.status = :status', { status: filters.status }); - } - - if (filters?.type) { - query.andWhere('n.type = :type', { type: filters.type }); - } - - query.orderBy('n.created_at', 'DESC'); - query.skip(filters?.offset || 0); - query.take(filters?.limit || 50); - - const [data, total] = await query.getManyAndCount(); - return { data, total }; - } - - async markAsRead(notificationId: string, userId: string): Promise { - const notification = await this.notificationRepository.findOne({ - where: { id: notificationId }, - }); - - if (!notification) { - throw new NotFoundException('Notification not found'); - } - - if (notification.userId !== userId) { - throw new ForbiddenException('Access denied'); - } - - notification.status = 'read'; - notification.readAt = new Date(); - await this.notificationRepository.save(notification); - } - - async markAllAsRead(userId: string): Promise { - const result = await this.notificationRepository - .createQueryBuilder() - .update(Notification) - .set({ status: 'read', readAt: new Date() }) - .where('user_id = :userId', { userId }) - .andWhere('status != :status', { status: 'read' }) - .execute(); - - return result.affected || 0; - } - - async getUnreadCount(userId: string): Promise { - return this.notificationRepository - .createQueryBuilder('n') - .where('n.user_id = :userId', { userId }) - .andWhere('n.status IN (:...statuses)', { statuses: ['pending', 'sent'] }) - .getCount(); - } - - async deleteNotification(notificationId: string, userId: string): Promise { - const notification = await this.notificationRepository.findOne({ - where: { id: notificationId, userId }, - }); - - if (!notification) { - throw new NotFoundException('Notification not found'); - } - - await this.notificationRepository.remove(notification); - } -} -``` - ---- - -## Paso 5: Crear MailService - -```typescript -// src/modules/mail/mail.service.ts -import { Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import * as nodemailer from 'nodemailer'; - -@Injectable() -export class MailService { - private transporter: nodemailer.Transporter | null = null; - private readonly logger = new Logger(MailService.name); - private readonly from: string; - - constructor(private readonly configService: ConfigService) { - this.from = configService.get('SMTP_FROM', 'App '); - this.initializeTransporter(); - } - - private initializeTransporter() { - const sendgridKey = this.configService.get('SENDGRID_API_KEY'); - - if (sendgridKey) { - this.transporter = nodemailer.createTransport({ - host: 'smtp.sendgrid.net', - port: 587, - auth: { user: 'apikey', pass: sendgridKey }, - }); - this.logger.log('Email initialized with SendGrid'); - return; - } - - const host = this.configService.get('SMTP_HOST'); - const user = this.configService.get('SMTP_USER'); - const pass = this.configService.get('SMTP_PASS'); - - if (host && user && pass) { - this.transporter = nodemailer.createTransport({ - host, - port: this.configService.get('SMTP_PORT', 587), - secure: this.configService.get('SMTP_SECURE', false), - auth: { user, pass }, - }); - this.logger.log('Email initialized with SMTP'); - } else { - this.logger.warn('Email not configured - emails will be logged only'); - } - } - - async sendEmail( - to: string | string[], - subject: string, - html: string, - ): Promise { - if (!this.transporter) { - this.logger.warn(`[MOCK EMAIL] To: ${to} | Subject: ${subject}`); - return false; - } - - try { - await this.transporter.sendMail({ - from: this.from, - to, - subject, - html, - }); - this.logger.log(`Email sent to ${to}`); - return true; - } catch (error) { - this.logger.error(`Failed to send email to ${to}`, error); - throw error; - } - } - - async sendNotificationEmail( - to: string, - title: string, - message: string, - actionUrl?: string, - ): Promise { - const html = ` - - - -

${title}

-

${message}

- ${actionUrl ? `Ver detalles` : ''} - - - `; - - return this.sendEmail(to, title, html); - } -} -``` - ---- - -## Paso 6: Crear NotificationsModule - -```typescript -// src/modules/notifications/notifications.module.ts -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Notification } from './entities/notification.entity'; -import { NotificationPreference } from './entities/notification-preference.entity'; -import { NotificationService } from './services/notification.service'; -import { NotificationsController } from './controllers/notifications.controller'; -import { MailModule } from '../mail/mail.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([Notification, NotificationPreference]), - MailModule, - ], - controllers: [NotificationsController], - providers: [NotificationService], - exports: [NotificationService], -}) -export class NotificationsModule {} -``` - ---- - -## Paso 7: Crear Controller - -```typescript -// src/modules/notifications/controllers/notifications.controller.ts -import { - Controller, - Get, - Post, - Delete, - Param, - Query, - UseGuards, - Request, -} from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { JwtAuthGuard } from '@/modules/auth/guards'; -import { NotificationService } from '../services/notification.service'; - -@ApiTags('Notifications') -@Controller('notifications') -@UseGuards(JwtAuthGuard) -@ApiBearerAuth() -export class NotificationsController { - constructor(private readonly notificationService: NotificationService) {} - - @Get() - async findAll( - @Request() req, - @Query('status') status?: string, - @Query('type') type?: string, - @Query('limit') limit?: number, - @Query('offset') offset?: number, - ) { - return this.notificationService.findAllByUser(req.user.id, { - status, - type, - limit: limit || 50, - offset: offset || 0, - }); - } - - @Get('unread-count') - async getUnreadCount(@Request() req) { - const count = await this.notificationService.getUnreadCount(req.user.id); - return { count }; - } - - @Post(':id/read') - async markAsRead(@Param('id') id: string, @Request() req) { - await this.notificationService.markAsRead(id, req.user.id); - return { success: true }; - } - - @Post('read-all') - async markAllAsRead(@Request() req) { - const count = await this.notificationService.markAllAsRead(req.user.id); - return { success: true, count }; - } - - @Delete(':id') - async delete(@Param('id') id: string, @Request() req) { - await this.notificationService.deleteNotification(id, req.user.id); - return { success: true }; - } -} -``` - ---- - -## Paso 8: Configurar Variables de Entorno - -```env -# Email - SMTP -SMTP_HOST=smtp.example.com -SMTP_PORT=587 -SMTP_USER=user -SMTP_PASS=password -SMTP_SECURE=false -SMTP_FROM="App Name " - -# Email - SendGrid (alternativo) -SENDGRID_API_KEY=SG.xxxxx - -# Push Notifications (generar con: npx web-push generate-vapid-keys) -VAPID_PUBLIC_KEY=BN... -VAPID_PRIVATE_KEY=... -VAPID_SUBJECT=mailto:admin@app.com - -# Frontend -FRONTEND_URL=https://app.example.com -``` - ---- - -## Checklist de Implementación - -- [ ] Dependencias npm instaladas -- [ ] DDL creado (schema + tablas) -- [ ] Entities alineadas con DDL -- [ ] NotificationService implementado -- [ ] MailService implementado -- [ ] Controller con endpoints -- [ ] NotificationsModule configurado -- [ ] Variables de entorno configuradas -- [ ] Build pasa sin errores -- [ ] Test de envío de email funciona - ---- - -## Opcional: Push Notifications - -```typescript -// src/modules/notifications/services/push-notification.service.ts -import { Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import * as webpush from 'web-push'; - -@Injectable() -export class PushNotificationService { - private readonly logger = new Logger(PushNotificationService.name); - - constructor(private readonly configService: ConfigService) { - const publicKey = configService.get('VAPID_PUBLIC_KEY'); - const privateKey = configService.get('VAPID_PRIVATE_KEY'); - const subject = configService.get('VAPID_SUBJECT'); - - if (publicKey && privateKey && subject) { - webpush.setVapidDetails(subject, publicKey, privateKey); - this.logger.log('Web Push initialized'); - } - } - - async sendPush( - subscription: webpush.PushSubscription, - payload: { title: string; body: string; url?: string }, - ): Promise { - try { - await webpush.sendNotification( - subscription, - JSON.stringify(payload), - ); - return true; - } catch (error) { - this.logger.error('Push notification failed', error); - return false; - } - } -} -``` - ---- - -## Código de Referencia - -Ver implementación completa en: -- `projects/gamilit/apps/backend/src/modules/notifications/` -- `projects/gamilit/apps/backend/src/modules/mail/` - ---- - -**Versión:** 1.0.0 -**Sistema:** SIMCO Catálogo diff --git a/shared/libs/notifications/README.md b/shared/libs/notifications/README.md deleted file mode 100644 index c7b46a260..000000000 --- a/shared/libs/notifications/README.md +++ /dev/null @@ -1,405 +0,0 @@ -# Sistema de Notificaciones - -**Versión:** 1.0.0 -**Origen:** projects/gamilit -**Estado:** Producción -**Última actualización:** 2025-12-08 - ---- - -## Descripción - -Sistema completo de notificaciones multi-canal: -- Notificaciones in-app (popups, bell icon) -- Notificaciones por email (SMTP, SendGrid) -- Push notifications (Web Push API nativo) -- Templates con interpolación de variables -- Preferencias por usuario y tipo -- Cola asíncrona para procesamiento - ---- - -## Características - -| Característica | Descripción | -|----------------|-------------| -| Multi-canal | in_app, email, push | -| Templates | Sistema de templates con variables | -| Preferencias | Por usuario y tipo de notificación | -| Cola Asíncrona | Procesamiento en background | -| Prioridades | low, normal, high, urgent | -| Expiración | Notificaciones con fecha de vencimiento | -| Logs | Registro de entregas y fallos | - ---- - -## Stack Tecnológico - -```yaml -backend: - framework: NestJS - orm: TypeORM - email: Nodemailer + SendGrid - push: web-push (Web Push API nativo VAPID) - -database: - engine: PostgreSQL - schema: notifications -``` - ---- - -## Dependencias NPM - -```json -{ - "nodemailer": "^6.x", - "@nestjs-modules/mailer": "^1.x", - "web-push": "^3.x", - "typeorm": "^0.3.x" -} -``` - ---- - -## Tablas Requeridas - -| Tabla | Propósito | -|-------|-----------| -| notifications.notifications | Notificaciones principales | -| notifications.notification_preferences | Preferencias por usuario/tipo | -| notifications.notification_templates | Templates con variables | -| notifications.notification_queue | Cola de procesamiento | -| notifications.notification_logs | Historial de entregas | -| notifications.user_devices | Dispositivos para push | - ---- - -## Estructura del Módulo - -``` -notifications/ -├── notifications.module.ts -├── controllers/ -│ ├── notifications.controller.ts # CRUD notificaciones -│ ├── notification-preferences.controller.ts -│ ├── notification-templates.controller.ts -│ └── notification-devices.controller.ts -├── services/ -│ ├── notification.service.ts # Servicio principal -│ ├── notification-preference.service.ts -│ ├── notification-template.service.ts -│ ├── notification-queue.service.ts # Cola asíncrona -│ ├── push-notification.service.ts # Web Push -│ └── user-device.service.ts -├── entities/ -│ ├── notification.entity.ts -│ ├── notification-preference.entity.ts -│ ├── notification-template.entity.ts -│ ├── notification-queue.entity.ts -│ ├── notification-log.entity.ts -│ └── user-device.entity.ts -└── dto/ - ├── create-notification.dto.ts - ├── notification-response.dto.ts - └── ... - -mail/ -├── mail.module.ts -├── mail.service.ts # Envío de emails -└── templates/ - └── notification.templates.ts # Templates HTML -``` - ---- - -## Canales de Notificación - -### 1. In-App - -```typescript -// Se muestra en bell icon y como popup -{ - channels: ['in_app'], - // El frontend consulta notificaciones no leídas - // WebSocket puede notificar en tiempo real -} -``` - -### 2. Email - -```typescript -// Envío vía SMTP o SendGrid -{ - channels: ['email'], - // Se usa template HTML - // Retry con backoff exponencial -} -``` - -### 3. Push - -```typescript -// Web Push API nativo (VAPID) -{ - channels: ['push'], - // No requiere Firebase/OneSignal - // Compatible con Chrome, Firefox, Safari 16.4+ -} -``` - ---- - -## Uso Rápido - -### 1. Crear notificación ad-hoc - -```typescript -import { NotificationService } from '@/modules/notifications/services'; - -await notificationService.create({ - userId: 'user-uuid', - title: 'Nuevo logro desbloqueado!', - message: 'Has completado 10 ejercicios', - type: 'achievement', - channels: ['in_app', 'email'], - priority: 'normal', - data: { - achievement_id: 'ach-001', - xp_reward: 100, - }, -}); -``` - -### 2. Usar template - -```typescript -await notificationService.sendFromTemplate({ - templateKey: 'achievement_unlocked', - userId: 'user-uuid', - variables: { - achievement_name: 'Primer Ejercicio', - xp_earned: '100', - }, - channels: ['in_app', 'push'], -}); -``` - -### 3. Obtener notificaciones del usuario - -```typescript -const { data, total } = await notificationService.findAllByUser(userId, { - status: 'sent', // pending, sent, read, failed - type: 'achievement', // filtrar por tipo - limit: 20, - offset: 0, -}); -``` - -### 4. Marcar como leída - -```typescript -await notificationService.markAsRead(notificationId, userId); -// o todas -await notificationService.markAllAsRead(userId); -``` - -### 5. Contador de no leídas - -```typescript -const unreadCount = await notificationService.getUnreadCount(userId); -``` - ---- - -## Sistema de Preferencias - -Los usuarios pueden configurar qué canales usar para cada tipo de notificación: - -```typescript -// Estructura de preferencia -{ - userId: 'user-uuid', - notificationType: 'achievement', // tipo de notificación - inAppEnabled: true, // mostrar en app - emailEnabled: false, // no enviar email - pushEnabled: true, // enviar push -} -``` - -### Tipos de notificación comunes - -| Tipo | Descripción | Canales por defecto | -|------|-------------|---------------------| -| achievement | Logros desbloqueados | in_app, push | -| rank_up | Subida de rango | in_app, email, push | -| assignment_due | Recordatorio de tarea | in_app, email | -| friend_request | Solicitud de amistad | in_app, push | -| system | Anuncios del sistema | in_app, email | -| password_reset | Reset de contraseña | email | - ---- - -## Servicio de Email - -### Configuración SMTP - -```env -SMTP_HOST=smtp.example.com -SMTP_PORT=587 -SMTP_USER=user@example.com -SMTP_PASS=password -SMTP_SECURE=false -SMTP_FROM="App Name " -``` - -### Configuración SendGrid - -```env -SENDGRID_API_KEY=SG.xxxxxx -``` - -### Métodos disponibles - -```typescript -class MailService { - // Email genérico - async sendEmail(to, subject, html, text?): Promise; - - // Email de notificación con template - async sendNotificationEmail(to, title, message, actionUrl?, actionText?): Promise; - - // Emails específicos - async sendPasswordResetEmail(email, token, userName?): Promise; - async sendVerificationEmail(email, token, userName?): Promise; - async sendWelcomeEmail(email, userName, role): Promise; -} -``` - ---- - -## Push Notifications (Web Push) - -### Configuración VAPID - -```env -VAPID_PUBLIC_KEY=BN... -VAPID_PRIVATE_KEY=... -VAPID_SUBJECT=mailto:admin@example.com -``` - -Generar claves: -```bash -npx web-push generate-vapid-keys -``` - -### Flujo de registro - -```typescript -// 1. Frontend obtiene subscription del navegador -const subscription = await registration.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: VAPID_PUBLIC_KEY, -}); - -// 2. Enviar subscription al backend -await fetch('/api/notifications/devices', { - method: 'POST', - body: JSON.stringify({ - subscription, - deviceType: 'web', - browser: 'Chrome', - }), -}); -``` - ---- - -## Variables de Entorno - -```env -# Email - SMTP -SMTP_HOST=smtp.example.com -SMTP_PORT=587 -SMTP_USER=user -SMTP_PASS=pass -SMTP_SECURE=false -SMTP_FROM="App " - -# Email - SendGrid (alternativo) -SENDGRID_API_KEY=SG.xxx - -# Push Notifications -VAPID_PUBLIC_KEY=BN... -VAPID_PRIVATE_KEY=... -VAPID_SUBJECT=mailto:admin@app.com - -# Frontend URL (para links en emails) -FRONTEND_URL=https://app.example.com -``` - ---- - -## Endpoints Principales - -| Método | Ruta | Descripción | -|--------|------|-------------| -| GET | /notifications | Listar notificaciones del usuario | -| GET | /notifications/unread-count | Contador de no leídas | -| POST | /notifications/:id/read | Marcar como leída | -| POST | /notifications/read-all | Marcar todas como leídas | -| DELETE | /notifications/:id | Eliminar notificación | -| GET | /notifications/preferences | Obtener preferencias | -| PUT | /notifications/preferences | Actualizar preferencias | -| POST | /notifications/devices | Registrar dispositivo push | -| DELETE | /notifications/devices/:id | Eliminar dispositivo | - ---- - -## Flujo de Envío - -``` -1. Crear notificación (service o trigger SQL) - │ - ▼ -2. Verificar preferencias del usuario - │ - ├─► in_app habilitado? → Guardar en BD - │ - ├─► email habilitado? → Encolar en notification_queue - │ - └─► push habilitado? → Encolar en notification_queue - │ - ▼ -3. Worker procesa cola (cron job) - │ - ├─► Enviar email via MailService - │ - └─► Enviar push via PushNotificationService - │ - ▼ -4. Registrar resultado en notification_logs -``` - ---- - -## Adaptaciones Necesarias - -1. **Tipos de notificación**: Definir tipos específicos del proyecto -2. **Templates**: Crear templates HTML para emails -3. **Canales**: Ajustar canales por defecto según necesidades -4. **VAPID keys**: Generar claves únicas para push -5. **Proveedor email**: Configurar SMTP o SendGrid - ---- - -## Referencias - -- [Web Push Protocol](https://developers.google.com/web/fundamentals/push-notifications) -- [Nodemailer](https://nodemailer.com/) -- [SendGrid Docs](https://docs.sendgrid.com/) - ---- - -**Mantenido por:** Sistema NEXUS -**Proyecto origen:** Gamilit Platform diff --git a/shared/libs/notifications/_reference/notification.service.reference.ts b/shared/libs/notifications/_reference/notification.service.reference.ts deleted file mode 100644 index b6a58ce63..000000000 --- a/shared/libs/notifications/_reference/notification.service.reference.ts +++ /dev/null @@ -1,232 +0,0 @@ -/** - * NOTIFICATION SERVICE - REFERENCE IMPLEMENTATION - * - * @description Servicio de notificaciones multi-canal. - * Soporta notificaciones in-app, email y push. - * - * @usage Copiar y adaptar según necesidades del proyecto. - * @origin gamilit/apps/backend/src/modules/notifications/services/notifications.service.ts - */ - -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -// Adaptar imports según proyecto -// import { Notification, NotificationPreference, UserDevice } from '../entities'; - -/** - * Tipos de notificación - */ -export enum NotificationTypeEnum { - INFO = 'info', - SUCCESS = 'success', - WARNING = 'warning', - ERROR = 'error', -} - -/** - * Canales de notificación - */ -export enum NotificationChannelEnum { - IN_APP = 'in_app', - EMAIL = 'email', - PUSH = 'push', -} - -@Injectable() -export class NotificationService { - private readonly logger = new Logger(NotificationService.name); - - constructor( - @InjectRepository(Notification, 'notifications') - private readonly notificationRepo: Repository, - - @InjectRepository(NotificationPreference, 'notifications') - private readonly preferenceRepo: Repository, - - // Inyectar servicios de email y push si están disponibles - // private readonly emailService: EmailService, - // private readonly pushService: PushNotificationService, - ) {} - - /** - * Enviar notificación a un usuario - * - * @param dto - Datos de la notificación - */ - async send(dto: CreateNotificationDto): Promise { - // 1. Obtener preferencias del usuario - const preferences = await this.getUserPreferences(dto.userId); - - // 2. Crear notificación en BD (siempre in-app) - const notification = this.notificationRepo.create({ - user_id: dto.userId, - type: dto.type, - title: dto.title, - message: dto.message, - data: dto.data, - is_read: false, - }); - await this.notificationRepo.save(notification); - - // 3. Enviar por canales adicionales según preferencias - if (preferences.email_enabled && dto.sendEmail !== false) { - await this.sendEmail(dto); - } - - if (preferences.push_enabled && dto.sendPush !== false) { - await this.sendPush(dto); - } - - return notification; - } - - /** - * Obtener notificaciones de un usuario - */ - async getByUser( - userId: string, - options?: { - unreadOnly?: boolean; - limit?: number; - offset?: number; - }, - ): Promise<{ notifications: Notification[]; total: number }> { - const query = this.notificationRepo - .createQueryBuilder('n') - .where('n.user_id = :userId', { userId }) - .orderBy('n.created_at', 'DESC'); - - if (options?.unreadOnly) { - query.andWhere('n.is_read = false'); - } - - const total = await query.getCount(); - - if (options?.limit) { - query.limit(options.limit); - } - if (options?.offset) { - query.offset(options.offset); - } - - const notifications = await query.getMany(); - - return { notifications, total }; - } - - /** - * Marcar notificación como leída - */ - async markAsRead(notificationId: string, userId: string): Promise { - await this.notificationRepo.update( - { id: notificationId, user_id: userId }, - { is_read: true, read_at: new Date() }, - ); - } - - /** - * Marcar todas las notificaciones como leídas - */ - async markAllAsRead(userId: string): Promise { - const result = await this.notificationRepo.update( - { user_id: userId, is_read: false }, - { is_read: true, read_at: new Date() }, - ); - return result.affected || 0; - } - - /** - * Obtener conteo de notificaciones no leídas - */ - async getUnreadCount(userId: string): Promise { - return this.notificationRepo.count({ - where: { user_id: userId, is_read: false }, - }); - } - - /** - * Eliminar notificación - */ - async delete(notificationId: string, userId: string): Promise { - await this.notificationRepo.delete({ - id: notificationId, - user_id: userId, - }); - } - - // ============ HELPERS PRIVADOS ============ - - private async getUserPreferences(userId: string): Promise { - const prefs = await this.preferenceRepo.findOne({ - where: { user_id: userId }, - }); - - // Retornar defaults si no tiene preferencias - return prefs || { - email_enabled: true, - push_enabled: true, - in_app_enabled: true, - }; - } - - private async sendEmail(dto: CreateNotificationDto): Promise { - try { - // Implementar envío de email - // await this.emailService.send({ - // to: dto.userEmail, - // subject: dto.title, - // template: 'notification', - // context: { message: dto.message, data: dto.data }, - // }); - this.logger.log(`Email notification sent to user ${dto.userId}`); - } catch (error) { - this.logger.error(`Failed to send email notification: ${error.message}`); - } - } - - private async sendPush(dto: CreateNotificationDto): Promise { - try { - // Implementar envío push - // await this.pushService.send(dto.userId, { - // title: dto.title, - // body: dto.message, - // data: dto.data, - // }); - this.logger.log(`Push notification sent to user ${dto.userId}`); - } catch (error) { - this.logger.error(`Failed to send push notification: ${error.message}`); - } - } -} - -// ============ TIPOS ============ - -interface CreateNotificationDto { - userId: string; - type: NotificationTypeEnum; - title: string; - message: string; - data?: Record; - sendEmail?: boolean; - sendPush?: boolean; -} - -interface UserPreferences { - email_enabled: boolean; - push_enabled: boolean; - in_app_enabled: boolean; -} - -interface Notification { - id: string; - user_id: string; - type: string; - title: string; - message: string; - data?: Record; - is_read: boolean; - read_at?: Date; - created_at: Date; -} diff --git a/shared/libs/package.json b/shared/libs/package.json deleted file mode 100644 index a94861d97..000000000 --- a/shared/libs/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@workspace/shared-libs", - "version": "1.0.0", - "description": "Shared libraries and reusable functionality catalog", - "private": true, - "workspaces": [ - "packages/*" - ], - "scripts": { - "build": "npm run build --workspaces --if-present", - "lint": "npm run lint --workspaces --if-present", - "test": "npm run test --workspaces --if-present" - }, - "devDependencies": { - "typescript": "^5.3.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/your-org/shared-libs.git" - }, - "license": "UNLICENSED" -} diff --git a/shared/libs/payments/IMPLEMENTATION.md b/shared/libs/payments/IMPLEMENTATION.md deleted file mode 100644 index 736597b2d..000000000 --- a/shared/libs/payments/IMPLEMENTATION.md +++ /dev/null @@ -1,1114 +0,0 @@ -# Guía de Implementación: Pagos con Stripe - -**Versión:** 1.0.0 -**Tiempo estimado:** 3-5 horas -**Complejidad:** Alta - ---- - -## Pre-requisitos - -- [ ] Cuenta de Stripe (test o live) -- [ ] Proyecto NestJS existente -- [ ] PostgreSQL como base de datos -- [ ] URL pública para webhooks (ngrok para desarrollo) - ---- - -## Paso 1: Configurar Stripe Dashboard - -### 1.1 Crear cuenta y obtener claves - -1. Ir a [dashboard.stripe.com](https://dashboard.stripe.com) -2. En **Developers > API keys**, obtener: - - Publishable key: `pk_test_xxx` o `pk_live_xxx` - - Secret key: `sk_test_xxx` o `sk_live_xxx` - -### 1.2 Crear productos y precios - -1. Ir a **Products > Add product** -2. Crear cada plan con sus precios: - -``` -Producto: Basic Plan -├── Price ID (monthly): price_basic_monthly -└── Price ID (yearly): price_basic_yearly - -Producto: Pro Plan -├── Price ID (monthly): price_pro_monthly -└── Price ID (yearly): price_pro_yearly -``` - -### 1.3 Configurar webhook - -1. Ir a **Developers > Webhooks** -2. Add endpoint: `https://api.example.com/webhooks/stripe` -3. Seleccionar eventos: - - `checkout.session.completed` - - `invoice.paid` - - `invoice.payment_failed` - - `customer.subscription.updated` - - `customer.subscription.deleted` -4. Copiar **Signing secret**: `whsec_xxx` - ---- - -## Paso 2: Instalar Dependencias - -```bash -npm install stripe -``` - ---- - -## Paso 3: Variables de Entorno - -```env -# .env -STRIPE_SECRET_KEY=sk_test_xxx -STRIPE_PUBLISHABLE_KEY=pk_test_xxx -STRIPE_WEBHOOK_SECRET=whsec_xxx - -# URLs -FRONTEND_URL=http://localhost:3000 -STRIPE_SUCCESS_URL=${FRONTEND_URL}/checkout/success -STRIPE_CANCEL_URL=${FRONTEND_URL}/pricing -``` - ---- - -## Paso 4: Crear Estructura de Directorios - -```bash -mkdir -p src/modules/payments/services -mkdir -p src/modules/payments/controllers -mkdir -p src/modules/payments/dto -mkdir -p src/modules/payments/types -``` - ---- - -## Paso 5: Definir Tipos - -```typescript -// src/modules/payments/types/payments.types.ts - -export type SubscriptionStatus = 'trialing' | 'active' | 'past_due' | 'cancelled' | 'unpaid'; -export type BillingCycle = 'monthly' | 'yearly'; -export type PaymentStatus = 'pending' | 'succeeded' | 'failed' | 'refunded'; - -export interface StripeCustomer { - id: string; - userId: string; - stripeCustomerId: string; - email?: string; - defaultPaymentMethodId?: string; - createdAt: Date; - updatedAt: Date; -} - -export interface SubscriptionPlan { - id: string; - name: string; - slug: string; - description?: string; - priceMonthly: number; - priceYearly?: number; - currency: string; - stripePriceIdMonthly?: string; - stripePriceIdYearly?: string; - stripeProductId?: string; - features: { name: string; included: boolean }[]; - isActive: boolean; - sortOrder: number; - createdAt: Date; - updatedAt: Date; -} - -export interface Subscription { - id: string; - userId: string; - planId: string; - stripeSubscriptionId?: string; - stripeCustomerId?: string; - status: SubscriptionStatus; - billingCycle: BillingCycle; - currentPeriodStart?: Date; - currentPeriodEnd?: Date; - cancelAtPeriodEnd: boolean; - cancelledAt?: Date; - currentPrice?: number; - currency: string; - createdAt: Date; - updatedAt: Date; -} - -export interface SubscriptionWithPlan extends Subscription { - plan: SubscriptionPlan; -} - -export interface CheckoutSession { - sessionId: string; - url: string; - expiresAt: Date; -} - -export interface CreateCheckoutInput { - userId: string; - planId: string; - billingCycle?: BillingCycle; - successUrl: string; - cancelUrl: string; - promoCode?: string; -} - -export interface BillingPortalSession { - url: string; - returnUrl: string; -} -``` - ---- - -## Paso 6: Crear Servicio de Stripe - -```typescript -// src/modules/payments/services/stripe.service.ts -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { ConfigService } from '@nestjs/config'; -import Stripe from 'stripe'; -import { StripeCustomerEntity } from '../entities/stripe-customer.entity'; -import { SubscriptionPlanEntity } from '../entities/subscription-plan.entity'; -import { - StripeCustomer, - CheckoutSession, - CreateCheckoutInput, - BillingPortalSession, -} from '../types/payments.types'; - -@Injectable() -export class StripeService { - private readonly stripe: Stripe; - private readonly logger = new Logger(StripeService.name); - - constructor( - @InjectRepository(StripeCustomerEntity) - private readonly customerRepo: Repository, - @InjectRepository(SubscriptionPlanEntity) - private readonly planRepo: Repository, - private readonly config: ConfigService, - ) { - this.stripe = new Stripe(config.get('STRIPE_SECRET_KEY'), { - apiVersion: '2023-10-16', - }); - } - - // ========================================================================== - // Customer Management - // ========================================================================== - - async getOrCreateCustomer(userId: string, email: string): Promise { - // Check existing - let customer = await this.customerRepo.findOne({ where: { userId } }); - - if (customer) { - return this.transformCustomer(customer); - } - - // Create in Stripe - const stripeCustomer = await this.stripe.customers.create({ - email, - metadata: { userId }, - }); - - // Save to DB - customer = this.customerRepo.create({ - userId, - stripeCustomerId: stripeCustomer.id, - email, - }); - await this.customerRepo.save(customer); - - this.logger.log(`Customer created: ${userId} -> ${stripeCustomer.id}`); - - return this.transformCustomer(customer); - } - - async getCustomerByUserId(userId: string): Promise { - const customer = await this.customerRepo.findOne({ where: { userId } }); - return customer ? this.transformCustomer(customer) : null; - } - - private transformCustomer(entity: StripeCustomerEntity): StripeCustomer { - return { - id: entity.id, - userId: entity.userId, - stripeCustomerId: entity.stripeCustomerId, - email: entity.email, - defaultPaymentMethodId: entity.defaultPaymentMethodId, - createdAt: entity.createdAt, - updatedAt: entity.updatedAt, - }; - } - - // ========================================================================== - // Checkout Sessions - // ========================================================================== - - async createCheckoutSession(input: CreateCheckoutInput): Promise { - // Get or create customer - const userEmail = await this.getUserEmail(input.userId); - const customer = await this.getOrCreateCustomer(input.userId, userEmail); - - // Get plan - const plan = await this.planRepo.findOne({ where: { id: input.planId } }); - if (!plan) { - throw new Error('Plan not found'); - } - - // Get price ID - const priceId = input.billingCycle === 'yearly' - ? plan.stripePriceIdYearly - : plan.stripePriceIdMonthly; - - if (!priceId) { - throw new Error('Stripe price not configured for this plan'); - } - - // Create session - const sessionConfig: Stripe.Checkout.SessionCreateParams = { - customer: customer.stripeCustomerId, - mode: 'subscription', - line_items: [{ price: priceId, quantity: 1 }], - success_url: input.successUrl, - cancel_url: input.cancelUrl, - metadata: { - userId: input.userId, - planId: input.planId, - billingCycle: input.billingCycle || 'monthly', - }, - subscription_data: { - metadata: { - userId: input.userId, - planId: input.planId, - }, - }, - }; - - // Apply promo code - if (input.promoCode) { - const couponId = await this.getCouponIdByCode(input.promoCode); - if (couponId) { - sessionConfig.discounts = [{ coupon: couponId }]; - } - } - - const session = await this.stripe.checkout.sessions.create(sessionConfig); - - this.logger.log(`Checkout session created: ${session.id}`); - - return { - sessionId: session.id, - url: session.url!, - expiresAt: new Date(session.expires_at * 1000), - }; - } - - private async getCouponIdByCode(code: string): Promise { - try { - const promotionCodes = await this.stripe.promotionCodes.list({ - code, - limit: 1, - }); - if (promotionCodes.data.length > 0 && promotionCodes.data[0].active) { - return promotionCodes.data[0].coupon.id; - } - return null; - } catch { - return null; - } - } - - private async getUserEmail(userId: string): Promise { - // Implementar según tu sistema de usuarios - // Por ejemplo: return this.userService.findById(userId).then(u => u.email); - throw new Error('Implement getUserEmail method'); - } - - // ========================================================================== - // Billing Portal - // ========================================================================== - - async createBillingPortalSession( - userId: string, - returnUrl: string, - ): Promise { - const customer = await this.getCustomerByUserId(userId); - if (!customer) { - throw new Error('Customer not found'); - } - - const session = await this.stripe.billingPortal.sessions.create({ - customer: customer.stripeCustomerId, - return_url: returnUrl, - }); - - return { - url: session.url, - returnUrl, - }; - } - - // ========================================================================== - // Payment Methods - // ========================================================================== - - async listPaymentMethods(userId: string): Promise { - const customer = await this.getCustomerByUserId(userId); - if (!customer) return []; - - const paymentMethods = await this.stripe.paymentMethods.list({ - customer: customer.stripeCustomerId, - type: 'card', - }); - - return paymentMethods.data; - } - - async setDefaultPaymentMethod(userId: string, paymentMethodId: string): Promise { - const customer = await this.getCustomerByUserId(userId); - if (!customer) throw new Error('Customer not found'); - - await this.stripe.customers.update(customer.stripeCustomerId, { - invoice_settings: { default_payment_method: paymentMethodId }, - }); - - await this.customerRepo.update( - { userId }, - { defaultPaymentMethodId: paymentMethodId }, - ); - } - - // ========================================================================== - // Subscriptions - // ========================================================================== - - async cancelSubscription( - stripeSubscriptionId: string, - immediately: boolean = false, - ): Promise { - if (immediately) { - return this.stripe.subscriptions.cancel(stripeSubscriptionId); - } - return this.stripe.subscriptions.update(stripeSubscriptionId, { - cancel_at_period_end: true, - }); - } - - async resumeSubscription(stripeSubscriptionId: string): Promise { - return this.stripe.subscriptions.update(stripeSubscriptionId, { - cancel_at_period_end: false, - }); - } - - async updateSubscriptionPlan( - stripeSubscriptionId: string, - newPriceId: string, - ): Promise { - const subscription = await this.stripe.subscriptions.retrieve(stripeSubscriptionId); - - return this.stripe.subscriptions.update(stripeSubscriptionId, { - items: [{ - id: subscription.items.data[0].id, - price: newPriceId, - }], - proration_behavior: 'create_prorations', - }); - } - - // ========================================================================== - // Webhooks - // ========================================================================== - - constructWebhookEvent(payload: Buffer, signature: string): Stripe.Event { - const webhookSecret = this.config.get('STRIPE_WEBHOOK_SECRET'); - return this.stripe.webhooks.constructEvent(payload, signature, webhookSecret); - } - - // ========================================================================== - // Invoices - // ========================================================================== - - async listInvoices(userId: string, limit: number = 10): Promise { - const customer = await this.getCustomerByUserId(userId); - if (!customer) return []; - - const invoices = await this.stripe.invoices.list({ - customer: customer.stripeCustomerId, - limit, - }); - - return invoices.data; - } - - // ========================================================================== - // Refunds - // ========================================================================== - - async createRefund( - chargeId: string, - amount?: number, - reason?: string, - ): Promise { - return this.stripe.refunds.create({ - charge: chargeId, - amount: amount ? Math.round(amount * 100) : undefined, - reason: reason as Stripe.RefundCreateParams.Reason, - }); - } -} -``` - ---- - -## Paso 7: Crear Servicio de Suscripciones - -```typescript -// src/modules/payments/services/subscription.service.ts -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, In } from 'typeorm'; -import { SubscriptionEntity } from '../entities/subscription.entity'; -import { SubscriptionPlanEntity } from '../entities/subscription-plan.entity'; -import { StripeService } from './stripe.service'; -import { - Subscription, - SubscriptionWithPlan, - SubscriptionPlan, - SubscriptionStatus, - BillingCycle, -} from '../types/payments.types'; - -@Injectable() -export class SubscriptionService { - private readonly logger = new Logger(SubscriptionService.name); - - constructor( - @InjectRepository(SubscriptionEntity) - private readonly subscriptionRepo: Repository, - @InjectRepository(SubscriptionPlanEntity) - private readonly planRepo: Repository, - private readonly stripeService: StripeService, - ) {} - - async getSubscriptionByUserId(userId: string): Promise { - const subscription = await this.subscriptionRepo.findOne({ - where: { - userId, - status: In(['active', 'trialing', 'past_due']), - }, - relations: ['plan'], - order: { createdAt: 'DESC' }, - }); - - if (!subscription) return null; - - return this.transformSubscription(subscription); - } - - async hasActiveSubscription(userId: string): Promise { - const count = await this.subscriptionRepo.count({ - where: { - userId, - status: In(['active', 'trialing']), - }, - }); - return count > 0; - } - - async getPlans(): Promise { - const plans = await this.planRepo.find({ - where: { isActive: true }, - order: { sortOrder: 'ASC' }, - }); - return plans.map(this.transformPlan); - } - - async cancelSubscription( - userId: string, - immediately: boolean = false, - reason?: string, - ): Promise { - const subscription = await this.getSubscriptionByUserId(userId); - if (!subscription) { - throw new Error('No active subscription found'); - } - - // Cancel in Stripe - if (subscription.stripeSubscriptionId) { - await this.stripeService.cancelSubscription( - subscription.stripeSubscriptionId, - immediately, - ); - } - - // Update local - const updateData: Partial = { - cancellationReason: reason, - }; - - if (immediately) { - updateData.status = 'cancelled'; - updateData.cancelledAt = new Date(); - } else { - updateData.cancelAtPeriodEnd = true; - } - - await this.subscriptionRepo.update({ id: subscription.id }, updateData); - - this.logger.log(`Subscription cancelled: ${subscription.id}`); - - return this.getSubscriptionByUserId(userId); - } - - async resumeSubscription(userId: string): Promise { - const subscription = await this.getSubscriptionByUserId(userId); - if (!subscription || !subscription.cancelAtPeriodEnd) { - throw new Error('No subscription to resume'); - } - - // Resume in Stripe - if (subscription.stripeSubscriptionId) { - await this.stripeService.resumeSubscription(subscription.stripeSubscriptionId); - } - - // Update local - await this.subscriptionRepo.update( - { id: subscription.id }, - { cancelAtPeriodEnd: false, cancellationReason: null }, - ); - - this.logger.log(`Subscription resumed: ${subscription.id}`); - - return this.getSubscriptionByUserId(userId); - } - - async changePlan( - userId: string, - newPlanId: string, - billingCycle?: BillingCycle, - ): Promise { - const subscription = await this.getSubscriptionByUserId(userId); - if (!subscription) { - throw new Error('No active subscription found'); - } - - const newPlan = await this.planRepo.findOne({ where: { id: newPlanId } }); - if (!newPlan) { - throw new Error('Plan not found'); - } - - const cycle = billingCycle || subscription.billingCycle; - const priceId = cycle === 'yearly' - ? newPlan.stripePriceIdYearly - : newPlan.stripePriceIdMonthly; - - // Update in Stripe - if (subscription.stripeSubscriptionId && priceId) { - await this.stripeService.updateSubscriptionPlan( - subscription.stripeSubscriptionId, - priceId, - ); - } - - // Update local - const newPrice = cycle === 'yearly' - ? (newPlan.priceYearly || newPlan.priceMonthly * 12) - : newPlan.priceMonthly; - - await this.subscriptionRepo.update( - { id: subscription.id }, - { planId: newPlanId, billingCycle: cycle, currentPrice: newPrice }, - ); - - this.logger.log(`Subscription plan changed: ${subscription.id} -> ${newPlanId}`); - - return this.getSubscriptionByUserId(userId); - } - - // Called from webhook handler - async createFromStripeEvent( - userId: string, - planId: string, - stripeSubscriptionId: string, - stripeCustomerId: string, - billingCycle: BillingCycle, - periodStart: Date, - periodEnd: Date, - ): Promise { - const plan = await this.planRepo.findOne({ where: { id: planId } }); - const price = billingCycle === 'yearly' - ? (plan.priceYearly || plan.priceMonthly * 12) - : plan.priceMonthly; - - const subscription = this.subscriptionRepo.create({ - userId, - planId, - stripeSubscriptionId, - stripeCustomerId, - status: 'active', - billingCycle, - currentPeriodStart: periodStart, - currentPeriodEnd: periodEnd, - currentPrice: price, - currency: plan.currency, - }); - - await this.subscriptionRepo.save(subscription); - - this.logger.log(`Subscription created from Stripe: ${subscription.id}`); - - return this.transformSubscription(subscription); - } - - async updateFromStripeEvent( - stripeSubscriptionId: string, - data: { - status?: SubscriptionStatus; - currentPeriodStart?: Date; - currentPeriodEnd?: Date; - cancelAtPeriodEnd?: boolean; - }, - ): Promise { - await this.subscriptionRepo.update( - { stripeSubscriptionId }, - data, - ); - } - - private transformSubscription(entity: SubscriptionEntity): SubscriptionWithPlan { - return { - id: entity.id, - userId: entity.userId, - planId: entity.planId, - stripeSubscriptionId: entity.stripeSubscriptionId, - stripeCustomerId: entity.stripeCustomerId, - status: entity.status, - billingCycle: entity.billingCycle, - currentPeriodStart: entity.currentPeriodStart, - currentPeriodEnd: entity.currentPeriodEnd, - cancelAtPeriodEnd: entity.cancelAtPeriodEnd, - cancelledAt: entity.cancelledAt, - currentPrice: entity.currentPrice, - currency: entity.currency, - createdAt: entity.createdAt, - updatedAt: entity.updatedAt, - plan: entity.plan ? this.transformPlan(entity.plan) : null, - }; - } - - private transformPlan(entity: SubscriptionPlanEntity): SubscriptionPlan { - return { - id: entity.id, - name: entity.name, - slug: entity.slug, - description: entity.description, - priceMonthly: entity.priceMonthly, - priceYearly: entity.priceYearly, - currency: entity.currency, - stripePriceIdMonthly: entity.stripePriceIdMonthly, - stripePriceIdYearly: entity.stripePriceIdYearly, - stripeProductId: entity.stripeProductId, - features: entity.features, - isActive: entity.isActive, - sortOrder: entity.sortOrder, - createdAt: entity.createdAt, - updatedAt: entity.updatedAt, - }; - } -} -``` - ---- - -## Paso 8: Crear Controller - -```typescript -// src/modules/payments/controllers/payments.controller.ts -import { - Controller, - Get, - Post, - Body, - Query, - UseGuards, - Request, - RawBodyRequest, - Headers, - BadRequestException, -} from '@nestjs/common'; -import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; -import { StripeService } from '../services/stripe.service'; -import { SubscriptionService } from '../services/subscription.service'; -import { CreateCheckoutDto, CancelSubscriptionDto } from '../dto'; -import Stripe from 'stripe'; - -@Controller('payments') -export class PaymentsController { - constructor( - private readonly stripeService: StripeService, - private readonly subscriptionService: SubscriptionService, - ) {} - - // ========================================================================== - // Plans - // ========================================================================== - - @Get('plans') - async getPlans() { - return this.subscriptionService.getPlans(); - } - - // ========================================================================== - // Subscription - // ========================================================================== - - @Get('subscription') - @UseGuards(JwtAuthGuard) - async getSubscription(@Request() req) { - return this.subscriptionService.getSubscriptionByUserId(req.user.id); - } - - @Post('checkout') - @UseGuards(JwtAuthGuard) - async createCheckout(@Request() req, @Body() dto: CreateCheckoutDto) { - return this.stripeService.createCheckoutSession({ - userId: req.user.id, - planId: dto.planId, - billingCycle: dto.billingCycle, - successUrl: dto.successUrl, - cancelUrl: dto.cancelUrl, - promoCode: dto.promoCode, - }); - } - - @Get('billing-portal') - @UseGuards(JwtAuthGuard) - async getBillingPortal(@Request() req, @Query('returnUrl') returnUrl: string) { - return this.stripeService.createBillingPortalSession(req.user.id, returnUrl); - } - - @Post('subscription/cancel') - @UseGuards(JwtAuthGuard) - async cancelSubscription( - @Request() req, - @Body() dto: CancelSubscriptionDto, - ) { - return this.subscriptionService.cancelSubscription( - req.user.id, - dto.immediately, - dto.reason, - ); - } - - @Post('subscription/resume') - @UseGuards(JwtAuthGuard) - async resumeSubscription(@Request() req) { - return this.subscriptionService.resumeSubscription(req.user.id); - } - - @Post('subscription/change-plan') - @UseGuards(JwtAuthGuard) - async changePlan( - @Request() req, - @Body() dto: { planId: string; billingCycle?: 'monthly' | 'yearly' }, - ) { - return this.subscriptionService.changePlan( - req.user.id, - dto.planId, - dto.billingCycle, - ); - } - - // ========================================================================== - // Payment Methods - // ========================================================================== - - @Get('payment-methods') - @UseGuards(JwtAuthGuard) - async listPaymentMethods(@Request() req) { - return this.stripeService.listPaymentMethods(req.user.id); - } - - // ========================================================================== - // Invoices - // ========================================================================== - - @Get('invoices') - @UseGuards(JwtAuthGuard) - async listInvoices(@Request() req, @Query('limit') limit?: number) { - return this.stripeService.listInvoices(req.user.id, limit || 10); - } -} - -// Webhook controller (separado para raw body) -@Controller('webhooks') -export class StripeWebhookController { - constructor( - private readonly stripeService: StripeService, - private readonly subscriptionService: SubscriptionService, - ) {} - - @Post('stripe') - async handleWebhook( - @Request() req: RawBodyRequest, - @Headers('stripe-signature') signature: string, - ) { - if (!signature) { - throw new BadRequestException('Missing stripe-signature header'); - } - - let event: Stripe.Event; - try { - event = this.stripeService.constructWebhookEvent(req.rawBody, signature); - } catch (err) { - throw new BadRequestException(`Webhook signature verification failed`); - } - - switch (event.type) { - case 'checkout.session.completed': - await this.handleCheckoutCompleted(event.data.object as Stripe.Checkout.Session); - break; - - case 'customer.subscription.updated': - await this.handleSubscriptionUpdated(event.data.object as Stripe.Subscription); - break; - - case 'customer.subscription.deleted': - await this.handleSubscriptionDeleted(event.data.object as Stripe.Subscription); - break; - - case 'invoice.payment_failed': - await this.handlePaymentFailed(event.data.object as Stripe.Invoice); - break; - } - - return { received: true }; - } - - private async handleCheckoutCompleted(session: Stripe.Checkout.Session) { - const { userId, planId, billingCycle } = session.metadata!; - - if (session.mode === 'subscription' && session.subscription) { - const stripeSubscription = await this.stripeService.getSubscription( - session.subscription as string, - ); - - await this.subscriptionService.createFromStripeEvent( - userId, - planId, - stripeSubscription.id, - session.customer as string, - billingCycle as 'monthly' | 'yearly', - new Date(stripeSubscription.current_period_start * 1000), - new Date(stripeSubscription.current_period_end * 1000), - ); - } - } - - private async handleSubscriptionUpdated(subscription: Stripe.Subscription) { - await this.subscriptionService.updateFromStripeEvent(subscription.id, { - status: this.mapStripeStatus(subscription.status), - currentPeriodStart: new Date(subscription.current_period_start * 1000), - currentPeriodEnd: new Date(subscription.current_period_end * 1000), - cancelAtPeriodEnd: subscription.cancel_at_period_end, - }); - } - - private async handleSubscriptionDeleted(subscription: Stripe.Subscription) { - await this.subscriptionService.updateFromStripeEvent(subscription.id, { - status: 'cancelled', - }); - } - - private async handlePaymentFailed(invoice: Stripe.Invoice) { - if (invoice.subscription) { - await this.subscriptionService.updateFromStripeEvent( - invoice.subscription as string, - { status: 'past_due' }, - ); - } - } - - private mapStripeStatus(status: Stripe.Subscription.Status): SubscriptionStatus { - const statusMap: Record = { - active: 'active', - trialing: 'trialing', - past_due: 'past_due', - canceled: 'cancelled', - unpaid: 'unpaid', - }; - return statusMap[status] || 'active'; - } -} -``` - ---- - -## Paso 9: Migraciones SQL - -```sql --- migrations/001_create_payments_tables.sql -CREATE SCHEMA IF NOT EXISTS financial; - --- Stripe Customers -CREATE TABLE financial.stripe_customers ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL UNIQUE REFERENCES public.users(id), - stripe_customer_id VARCHAR(100) NOT NULL UNIQUE, - email VARCHAR(255), - default_payment_method_id VARCHAR(100), - metadata JSONB DEFAULT '{}', - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); - --- Subscription Plans -CREATE TABLE financial.subscription_plans ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(100) NOT NULL, - slug VARCHAR(50) NOT NULL UNIQUE, - description TEXT, - price_monthly DECIMAL(10, 2) NOT NULL, - price_yearly DECIMAL(10, 2), - currency VARCHAR(3) DEFAULT 'usd', - stripe_price_id_monthly VARCHAR(100), - stripe_price_id_yearly VARCHAR(100), - stripe_product_id VARCHAR(100), - features JSONB DEFAULT '[]', - is_active BOOLEAN DEFAULT true, - sort_order INTEGER DEFAULT 0, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); - --- Subscriptions -CREATE TABLE financial.subscriptions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES public.users(id), - plan_id UUID NOT NULL REFERENCES financial.subscription_plans(id), - stripe_subscription_id VARCHAR(100) UNIQUE, - stripe_customer_id VARCHAR(100), - status VARCHAR(20) NOT NULL DEFAULT 'active', - billing_cycle VARCHAR(20) NOT NULL DEFAULT 'monthly', - current_period_start TIMESTAMP WITH TIME ZONE, - current_period_end TIMESTAMP WITH TIME ZONE, - cancel_at_period_end BOOLEAN DEFAULT false, - cancelled_at TIMESTAMP WITH TIME ZONE, - cancellation_reason TEXT, - current_price DECIMAL(10, 2), - currency VARCHAR(3) DEFAULT 'usd', - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); - --- Indexes -CREATE INDEX idx_subscriptions_user ON financial.subscriptions(user_id); -CREATE INDEX idx_subscriptions_status ON financial.subscriptions(status); -CREATE INDEX idx_subscriptions_stripe ON financial.subscriptions(stripe_subscription_id); - --- Trigger for updated_at -CREATE TRIGGER update_subscriptions_timestamp - BEFORE UPDATE ON financial.subscriptions - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); -``` - ---- - -## Paso 10: Configurar Raw Body para Webhooks - -```typescript -// main.ts -import { NestFactory } from '@nestjs/core'; -import { AppModule } from './app.module'; - -async function bootstrap() { - const app = await NestFactory.create(AppModule, { - rawBody: true, // Enable raw body for webhooks - }); - - await app.listen(3000); -} -bootstrap(); -``` - ---- - -## Variables de Entorno - -```env -# Stripe -STRIPE_SECRET_KEY=sk_test_xxx -STRIPE_PUBLISHABLE_KEY=pk_test_xxx -STRIPE_WEBHOOK_SECRET=whsec_xxx - -# URLs -FRONTEND_URL=http://localhost:3000 -``` - ---- - -## Checklist de Implementación - -- [ ] Cuenta Stripe configurada -- [ ] Productos y precios creados en Stripe -- [ ] Webhook endpoint configurado -- [ ] Dependencia `stripe` instalada -- [ ] Variables de entorno configuradas -- [ ] Entidades creadas (StripeCustomer, SubscriptionPlan, Subscription) -- [ ] StripeService implementado -- [ ] SubscriptionService implementado -- [ ] Controllers implementados -- [ ] Webhook handler implementado -- [ ] Raw body habilitado para webhooks -- [ ] Migraciones ejecutadas -- [ ] Build pasa sin errores -- [ ] Test: crear checkout session -- [ ] Test: webhook recibido correctamente - ---- - -## Verificar Funcionamiento - -```bash -# Usar Stripe CLI para testing local -stripe listen --forward-to localhost:3000/webhooks/stripe - -# Trigger test events -stripe trigger checkout.session.completed -stripe trigger invoice.paid -``` - ---- - -## Troubleshooting - -### "No signatures found matching the expected signature" -- Verificar STRIPE_WEBHOOK_SECRET -- Verificar que raw body está habilitado - -### Webhook no llega -- Verificar URL en Stripe dashboard -- Usar ngrok para desarrollo local - -### Subscription not created -- Verificar metadata en checkout session -- Revisar logs del webhook handler - ---- - -**Versión:** 1.0.0 -**Sistema:** SIMCO Catálogo diff --git a/shared/libs/payments/README.md b/shared/libs/payments/README.md deleted file mode 100644 index 6e782310b..000000000 --- a/shared/libs/payments/README.md +++ /dev/null @@ -1,468 +0,0 @@ -# Integración de Pagos - -**Versión:** 1.0.0 -**Origen:** projects/trading-platform -**Estado:** Producción -**Última actualización:** 2025-12-08 - ---- - -## Descripción - -Sistema completo de pagos con integración Stripe: -- Gestión de clientes en Stripe -- Checkout sessions para pagos seguros -- Suscripciones con ciclos de facturación -- Métodos de pago (tarjetas) -- Billing portal de autoservicio -- Webhooks para eventos de Stripe -- Wallet interno para balance de usuario -- Códigos promocionales - ---- - -## Características - -| Característica | Descripción | -|----------------|-------------| -| Stripe Checkout | Hosted checkout pages | -| Suscripciones | Monthly/yearly con trial | -| Payment Intents | One-time payments | -| Billing Portal | Self-service portal | -| Webhooks | Eventos en tiempo real | -| Wallet | Balance interno | -| Promo codes | Descuentos y cupones | -| Invoices | Facturas automáticas | -| Refunds | Reembolsos parciales/totales | - ---- - -## Stack Tecnológico - -```yaml -backend: - framework: NestJS / Express - payment_processor: Stripe - database: PostgreSQL - -packages: - - "stripe" -``` - ---- - -## Dependencias NPM - -```json -{ - "stripe": "^14.x" -} -``` - ---- - -## Tablas Requeridas - -| Tabla | Propósito | -|-------|-----------| -| financial.stripe_customers | Relación user-customer Stripe | -| financial.subscription_plans | Planes disponibles | -| financial.subscriptions | Suscripciones activas | -| financial.payments | Historial de pagos | -| financial.invoices | Facturas | -| financial.wallets | Balance del usuario | -| financial.wallet_transactions | Movimientos del wallet | -| financial.promo_codes | Códigos promocionales | - ---- - -## Estructura del Módulo - -``` -payments/ -├── services/ -│ ├── stripe.service.ts # Integración con Stripe API -│ ├── subscription.service.ts # Gestión de suscripciones -│ └── wallet.service.ts # Wallet interno -├── controllers/ -│ └── payments.controller.ts # Endpoints REST -├── webhooks/ -│ └── stripe.webhook.ts # Handler de webhooks -├── types/ -│ └── payments.types.ts # Tipos e interfaces -└── dto/ - ├── create-checkout.dto.ts - └── subscription.dto.ts -``` - ---- - -## Modelos de Datos - -### StripeCustomer - -```typescript -interface StripeCustomer { - id: string; - userId: string; - stripeCustomerId: string; // cus_xxx - email?: string; - defaultPaymentMethodId?: string; // pm_xxx - metadata?: Record; - createdAt: Date; - updatedAt: Date; -} -``` - -### SubscriptionPlan - -```typescript -interface SubscriptionPlan { - id: string; - name: string; // "Pro Plan" - slug: string; // "pro" - description?: string; - priceMonthly: number; // 29.99 - priceYearly?: number; // 299.99 - currency: string; // "usd" - stripePriceIdMonthly?: string; // price_xxx - stripePriceIdYearly?: string; // price_xxx - stripeProductId?: string; // prod_xxx - features: PlanFeature[]; - isActive: boolean; - sortOrder: number; -} -``` - -### Subscription - -```typescript -interface Subscription { - id: string; - userId: string; - planId: string; - stripeSubscriptionId?: string; // sub_xxx - stripeCustomerId?: string; // cus_xxx - status: 'trialing' | 'active' | 'past_due' | 'cancelled' | 'unpaid'; - billingCycle: 'monthly' | 'yearly'; - currentPeriodStart?: Date; - currentPeriodEnd?: Date; - trialStart?: Date; - trialEnd?: Date; - cancelAtPeriodEnd: boolean; - cancelledAt?: Date; - cancellationReason?: string; - currentPrice?: number; - currency: string; -} -``` - -### Wallet - -```typescript -interface Wallet { - id: string; - userId: string; - currency: string; - balance: number; - availableBalance: number; - pendingBalance: number; - isActive: boolean; - dailyWithdrawalLimit: number; - monthlyWithdrawalLimit: number; -} -``` - ---- - -## Flujo de Pago con Stripe Checkout - -``` -1. Usuario selecciona plan - │ - ▼ -2. Backend crea Checkout Session - - Obtiene/crea customer en Stripe - - Configura line_items con price_id - - Define success_url y cancel_url - │ - ▼ -3. Redirect a Stripe Checkout - │ - ▼ -4. Usuario completa pago - │ - ▼ -5. Stripe envía webhook - - checkout.session.completed - - invoice.paid - │ - ▼ -6. Backend procesa webhook - - Crea/actualiza suscripción - - Registra pago - - Activa features del plan -``` - ---- - -## Uso Rápido - -### 1. Crear cliente en Stripe - -```typescript -const customer = await stripeService.getOrCreateCustomer( - userId, - userEmail -); -// { stripeCustomerId: 'cus_xxx', ... } -``` - -### 2. Crear checkout session - -```typescript -const session = await stripeService.createCheckoutSession({ - userId: 'user-uuid', - planId: 'plan-uuid', - billingCycle: 'monthly', - successUrl: 'https://app.com/success?session_id={CHECKOUT_SESSION_ID}', - cancelUrl: 'https://app.com/pricing', - promoCode: 'SUMMER20', // opcional -}); - -// Redirect usuario a session.url -``` - -### 3. Verificar suscripción - -```typescript -const subscription = await subscriptionService.getSubscriptionByUserId(userId); - -if (subscription?.status === 'active') { - // Usuario tiene suscripción activa - const hasFeature = subscription.plan.apiAccess; -} -``` - -### 4. Billing Portal (autoservicio) - -```typescript -const portal = await stripeService.createBillingPortalSession( - userId, - 'https://app.com/dashboard' -); - -// Redirect a portal.url -// Usuario puede: actualizar tarjeta, ver facturas, cancelar -``` - -### 5. Cancelar suscripción - -```typescript -// Al final del período -await subscriptionService.cancelSubscription(userId, false, 'User requested'); - -// Inmediatamente -await subscriptionService.cancelSubscription(userId, true); -``` - -### 6. Cambiar de plan - -```typescript -await subscriptionService.changePlan(userId, newPlanId, 'yearly'); -``` - ---- - -## Webhook Handler - -```typescript -// POST /webhooks/stripe -async handleStripeWebhook(req: Request) { - const signature = req.headers['stripe-signature']; - const event = stripe.webhooks.constructEvent( - req.body, - signature, - process.env.STRIPE_WEBHOOK_SECRET - ); - - switch (event.type) { - case 'checkout.session.completed': - await this.handleCheckoutCompleted(event.data.object); - break; - - case 'invoice.paid': - await this.handleInvoicePaid(event.data.object); - break; - - case 'invoice.payment_failed': - await this.handlePaymentFailed(event.data.object); - break; - - case 'customer.subscription.updated': - await this.handleSubscriptionUpdated(event.data.object); - break; - - case 'customer.subscription.deleted': - await this.handleSubscriptionDeleted(event.data.object); - break; - } - - return { received: true }; -} - -private async handleCheckoutCompleted(session: Stripe.Checkout.Session) { - const { userId, planId, billingCycle } = session.metadata!; - - // Crear suscripción local - await db.query(` - INSERT INTO financial.subscriptions ( - user_id, plan_id, stripe_subscription_id, stripe_customer_id, - status, billing_cycle, current_period_start, current_period_end - ) VALUES ($1, $2, $3, $4, 'active', $5, $6, $7) - `, [userId, planId, session.subscription, session.customer, billingCycle, ...]); -} -``` - ---- - -## Variables de Entorno - -```env -# Stripe -STRIPE_SECRET_KEY=sk_live_xxx # o sk_test_xxx -STRIPE_PUBLISHABLE_KEY=pk_live_xxx # para frontend -STRIPE_WEBHOOK_SECRET=whsec_xxx - -# URLs -FRONTEND_URL=https://app.example.com -STRIPE_SUCCESS_URL=${FRONTEND_URL}/success -STRIPE_CANCEL_URL=${FRONTEND_URL}/pricing -``` - ---- - -## Endpoints Principales - -| Método | Ruta | Descripción | -|--------|------|-------------| -| GET | /plans | Listar planes disponibles | -| GET | /plans/:id | Obtener plan por ID | -| GET | /subscription | Obtener suscripción del usuario | -| POST | /checkout/subscription | Crear checkout para suscripción | -| POST | /checkout/course | Crear checkout para curso | -| GET | /billing-portal | Obtener URL del billing portal | -| POST | /subscription/cancel | Cancelar suscripción | -| POST | /subscription/resume | Reactivar suscripción | -| POST | /subscription/change-plan | Cambiar de plan | -| GET | /invoices | Listar facturas del usuario | -| GET | /payment-methods | Listar métodos de pago | -| POST | /payment-methods | Agregar método de pago | -| DELETE | /payment-methods/:id | Eliminar método de pago | -| POST | /webhooks/stripe | Webhook handler | - ---- - -## Configuración en Stripe Dashboard - -### 1. Productos y Precios - -``` -Producto: Pro Plan (prod_xxx) -├── Precio mensual: $29.99/month (price_monthly_xxx) -└── Precio anual: $299.99/year (price_yearly_xxx) -``` - -### 2. Webhook Endpoints - -``` -Endpoint: https://api.example.com/webhooks/stripe -Events: -- checkout.session.completed -- invoice.paid -- invoice.payment_failed -- customer.subscription.updated -- customer.subscription.deleted -- customer.subscription.created -``` - -### 3. Customer Portal - -Configurar en Settings > Billing > Customer portal: -- Allow customers to update payment methods -- Allow customers to view invoice history -- Allow customers to cancel subscriptions - ---- - -## Wallet (Balance Interno) - -### Depositar fondos - -```typescript -await walletService.deposit({ - userId: 'user-uuid', - amount: 100.00, - currency: 'usd', - description: 'Initial deposit', -}); -``` - -### Usar balance para pago - -```typescript -const canPay = await walletService.canAfford(userId, 50.00); -if (canPay) { - await walletService.debit(userId, 50.00, 'Course purchase'); -} -``` - -### Reembolso al wallet - -```typescript -await walletService.credit(userId, 25.00, 'Partial refund'); -``` - ---- - -## Códigos Promocionales - -```typescript -// Validar código -const result = await promoService.validateCode('SUMMER20', { - userId, - planId, - amount: 29.99, -}); - -if (result.valid) { - // Aplicar descuento - const finalPrice = 29.99 - result.discountAmount; -} -``` - ---- - -## Adaptaciones Necesarias - -1. **Productos en Stripe**: Crear productos y precios en dashboard -2. **Webhook URL**: Configurar endpoint público -3. **Planes**: Ajustar features según tu modelo de negocio -4. **Moneda**: Configurar currency (usd, eur, mxn, etc.) -5. **Trial**: Configurar período de prueba si aplica -6. **Tax**: Configurar impuestos si aplica (Stripe Tax) - ---- - -## Referencias - -- [Stripe API Reference](https://stripe.com/docs/api) -- [Stripe Checkout](https://stripe.com/docs/payments/checkout) -- [Stripe Subscriptions](https://stripe.com/docs/billing/subscriptions) -- [Stripe Webhooks](https://stripe.com/docs/webhooks) -- [Stripe Customer Portal](https://stripe.com/docs/billing/subscriptions/customer-portal) - ---- - -**Mantenido por:** Sistema NEXUS -**Proyecto origen:** Trading Platform diff --git a/shared/libs/payments/_reference/README.md b/shared/libs/payments/_reference/README.md deleted file mode 100644 index e45dff197..000000000 --- a/shared/libs/payments/_reference/README.md +++ /dev/null @@ -1,405 +0,0 @@ -# PAYMENTS - REFERENCE IMPLEMENTATION - -**Versión:** 1.0.0 | **Fecha:** 2025-12-12 | **Nivel:** Catalog (3) - ---- - -## ÍNDICE DE ARCHIVOS - -| Archivo | Descripción | LOC | Patrón Principal | -|---------|-------------|-----|------------------| -| `payment.service.reference.ts` | Servicio completo de pagos con Stripe | 296 | Checkout, Subscriptions, Webhooks | - ---- - -## CÓMO USAR - -### Flujo de adopción recomendado - -```yaml -PASO_1: Configurar cuenta Stripe - - Crear cuenta en https://stripe.com - - Obtener API keys (test + production) - - Configurar webhook endpoint - - Crear productos y precios en dashboard - -PASO_2: Instalar dependencias - - npm install stripe - - npm install @nestjs/config (si no está instalado) - -PASO_3: Configurar variables de entorno - - STRIPE_SECRET_KEY: sk_test_... (o sk_live_...) - - STRIPE_WEBHOOK_SECRET: whsec_... (del dashboard) - - STRIPE_API_VERSION: 2023-10-16 (o más reciente) - -PASO_4: Copiar y adaptar archivo - - Copiar payment.service.reference.ts → payment.service.ts - - Ajustar imports de entidades (Payment, Subscription, Customer) - - Configurar conexión a BD correcta (@InjectRepository) - -PASO_5: Implementar entidades requeridas - - Customer: user_id, stripe_customer_id, email - - Payment: user_id, stripe_payment_id, amount, currency, status - - Subscription: user_id, stripe_subscription_id, status, periods - -PASO_6: Configurar webhook endpoint - - Crear endpoint POST /webhooks/stripe - - Usar raw body (no JSON parsed) - - Verificar firma con stripe.webhooks.constructEvent() - -PASO_7: Validar integración - - Probar checkout session en modo test - - Simular webhooks desde Stripe CLI - - Verificar pagos en BD y dashboard Stripe -``` - ---- - -## PATRONES IMPLEMENTADOS - -### 1. Checkout Session (Pago único o suscripción) - -**Flujo:** -``` -1. Frontend solicita checkout session -2. Backend crea session en Stripe -3. Backend retorna URL de checkout -4. Usuario completa pago en Stripe -5. Stripe envía webhook checkout.session.completed -6. Backend guarda payment en BD -``` - -**Ejemplo de uso:** -```typescript -// En tu controller -@Post('create-checkout') -async createCheckout(@Body() dto: CreateCheckoutDto, @Req() req) { - const session = await this.paymentService.createCheckoutSession({ - userId: req.user.id, - email: req.user.email, - priceId: dto.priceId, // Del dashboard Stripe - successUrl: `${process.env.APP_URL}/payment/success`, - cancelUrl: `${process.env.APP_URL}/payment/cancel`, - mode: 'payment', // o 'subscription' - }); - - return { checkoutUrl: session.url }; -} -``` - -### 2. Suscripciones - -**Crear suscripción:** -```typescript -const subscription = await this.paymentService.createSubscription({ - userId: user.id, - email: user.email, - priceId: 'price_monthly_premium', -}); - -// Suscripción queda en estado "incomplete" -// Usuario debe completar pago (requiere payment method) -``` - -**Cancelar suscripción:** -```typescript -await this.paymentService.cancelSubscription( - subscriptionId, - userId -); -// Se cancela al final del periodo actual -``` - -**Obtener suscripción activa:** -```typescript -const subscription = await this.paymentService.getActiveSubscription(userId); -if (subscription) { - // Usuario tiene plan premium -} -``` - -### 3. Webhooks - -**Eventos soportados:** - -| Evento Stripe | Handler | Acción | -|---------------|---------|--------| -| `checkout.session.completed` | `handleCheckoutCompleted` | Guarda Payment en BD | -| `invoice.paid` | `handleInvoicePaid` | Actualiza Subscription a 'active' | -| `invoice.payment_failed` | `handlePaymentFailed` | Marca Subscription como 'past_due' | -| `customer.subscription.updated` | `handleSubscriptionUpdate` | Sincroniza estado de subscription | -| `customer.subscription.deleted` | `handleSubscriptionUpdate` | Sincroniza cancelación | - -**Configurar webhook controller:** -```typescript -@Controller('webhooks') -export class WebhookController { - constructor(private readonly paymentService: PaymentService) {} - - @Post('stripe') - async handleStripeWebhook( - @Headers('stripe-signature') signature: string, - @Req() req: RawBodyRequest, - ) { - await this.paymentService.handleWebhook( - signature, - req.rawBody, // Importante: usar raw body - ); - return { received: true }; - } -} -``` - ---- - -## NOTAS DE ADAPTACIÓN - -### Variables a reemplazar - -```typescript -// Entidades -Payment → Tu entidad de pagos -Subscription → Tu entidad de suscripciones -Customer → Tu entidad de clientes Stripe - -// DTOs -CreateCheckoutDto → Tu DTO de checkout -CreateSubscriptionDto → Tu DTO de suscripción -``` - -### Configurar raw body para webhooks - -En `main.ts`: -```typescript -const app = await NestFactory.create(AppModule, { - rawBody: true, // Habilitar raw body -}); - -// O usar middleware específico: -app.use('/webhooks/stripe', express.raw({ type: 'application/json' })); -``` - -### Esquema de base de datos - -```sql --- Tabla customers -CREATE TABLE customers ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID UNIQUE REFERENCES users(id) ON DELETE CASCADE, - stripe_customer_id VARCHAR(255) UNIQUE NOT NULL, - email VARCHAR(255) NOT NULL, - created_at TIMESTAMP DEFAULT NOW() -); - --- Tabla payments -CREATE TABLE payments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES users(id) ON DELETE CASCADE, - stripe_payment_id VARCHAR(255) UNIQUE NOT NULL, - amount INTEGER NOT NULL, -- En centavos (ej: 1000 = $10.00) - currency VARCHAR(3) DEFAULT 'usd', - status VARCHAR(50) NOT NULL, -- completed, pending, failed - metadata JSONB, - created_at TIMESTAMP DEFAULT NOW() -); - --- Tabla subscriptions -CREATE TABLE subscriptions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES users(id) ON DELETE CASCADE, - stripe_subscription_id VARCHAR(255) UNIQUE NOT NULL, - stripe_customer_id VARCHAR(255) NOT NULL, - status VARCHAR(50) NOT NULL, -- active, past_due, canceled, incomplete - current_period_start TIMESTAMP NOT NULL, - current_period_end TIMESTAMP NOT NULL, - cancel_at_period_end BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() -); - -CREATE INDEX idx_subscriptions_user_status ON subscriptions(user_id, status); -``` - ---- - -## CASOS DE USO COMUNES - -### 1. Implementar plan de suscripción mensual - -```typescript -// 1. Crear precio en Stripe dashboard: -// - Producto: "Premium Plan" -// - Precio: $9.99/mes -// - ID: price_premium_monthly - -// 2. Endpoint de suscripción -@Post('subscribe') -async subscribe(@Req() req) { - const subscription = await this.paymentService.createSubscription({ - userId: req.user.id, - email: req.user.email, - priceId: 'price_premium_monthly', - }); - - return { - subscriptionId: subscription.id, - status: subscription.status, - }; -} - -// 3. Verificar estado en guards/middlewares -@Injectable() -export class PremiumGuard implements CanActivate { - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - const subscription = await this.paymentService.getActiveSubscription(req.user.id); - return !!subscription; - } -} -``` - -### 2. Pago único de producto - -```typescript -@Post('buy-course') -async buyCourse(@Body() dto: BuyCourseDto, @Req() req) { - const session = await this.paymentService.createCheckoutSession({ - userId: req.user.id, - email: req.user.email, - priceId: dto.coursePriceId, - mode: 'payment', // Pago único - successUrl: `${process.env.APP_URL}/courses/${dto.courseId}/access`, - cancelUrl: `${process.env.APP_URL}/courses/${dto.courseId}`, - metadata: { - courseId: dto.courseId, - type: 'course_purchase', - }, - }); - - return { checkoutUrl: session.url }; -} - -// En webhook handler personalizado: -private async handleCheckoutCompleted(session: Stripe.Checkout.Session) { - if (session.metadata?.type === 'course_purchase') { - await this.coursesService.grantAccess( - session.metadata.userId, - session.metadata.courseId, - ); - } -} -``` - -### 3. Pruebas locales con Stripe CLI - -```bash -# Instalar Stripe CLI -brew install stripe/stripe-cli/stripe - -# Login -stripe login - -# Escuchar webhooks (forward a localhost) -stripe listen --forward-to http://localhost:3000/webhooks/stripe - -# Copiar webhook secret que muestra (whsec_...) -# Actualizar .env: STRIPE_WEBHOOK_SECRET=whsec_... - -# Simular eventos -stripe trigger checkout.session.completed -stripe trigger invoice.paid -``` - ---- - -## MANEJO DE ERRORES COMUNES - -### Error: "No such price" -```typescript -// Solución: Verificar que el priceId existe en Stripe dashboard -// Usar precios de test (price_test_...) en desarrollo -``` - -### Error: "Webhook signature verification failed" -```typescript -// Solución: Asegurar que se usa raw body -// Verificar que STRIPE_WEBHOOK_SECRET es correcto -// En desarrollo, usar Stripe CLI para obtener secret local -``` - -### Error: "Customer already exists" -```typescript -// Solución: Ya manejado en getOrCreateCustomer() -// Busca customer existente antes de crear -``` - -### Suscripción queda en "incomplete" -```typescript -// Solución: Usuario debe completar payment method -// Usar checkout.session para suscripciones (más fácil) -// O implementar setup intent para agregar payment method -``` - ---- - -## CHECKLIST DE VALIDACIÓN - -Antes de marcar como completo: - -- [ ] Build pasa: `npm run build` -- [ ] Lint pasa: `npm run lint` -- [ ] Variables de entorno configuradas (STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET) -- [ ] Entidades creadas en BD (Customer, Payment, Subscription) -- [ ] Checkout session funciona (modo test) -- [ ] Webhook endpoint configurado con raw body -- [ ] Webhooks verifican firma correctamente -- [ ] Eventos se guardan en BD -- [ ] Probado con Stripe CLI local -- [ ] Dashboard Stripe muestra eventos correctamente - ---- - -## REFERENCIAS CRUZADAS - -### Dependencias en @CATALOG - -- **auth**: Para autenticar usuarios en endpoints de pago -- **notifications**: Notificar usuario sobre pagos/suscripciones -- **multi-tenancy**: Pagos por tenant (empresas) - -### Relacionado con SIMCO - -- **@OP_BACKEND**: Operaciones de backend (crear service, webhooks) -- **@SIMCO-REUTILIZAR**: Este catálogo es candidato para reutilización -- **@SIMCO-VALIDAR**: Validar con Stripe CLI antes de deploy - -### Documentación adicional - -- Stripe API: https://stripe.com/docs/api -- Checkout Sessions: https://stripe.com/docs/payments/checkout -- Webhooks: https://stripe.com/docs/webhooks -- Testing: https://stripe.com/docs/testing - ---- - -## SEGURIDAD - -### Mejores prácticas implementadas: - -1. **Webhook signature verification**: Evita requests maliciosos -2. **Refresh token hashing**: Nunca guardar tokens planos -3. **Metadata validation**: Validar userId en webhooks -4. **API versioning**: Fijar versión de API Stripe -5. **Idempotency**: Webhooks pueden repetirse (manejar duplicados) - -### Recomendaciones adicionales: - -- Usar HTTPS en producción (requerido por Stripe) -- Limitar rate de endpoints de pago -- Logging detallado de eventos Stripe -- Monitorear webhooks fallidos en dashboard -- Implementar retry logic para webhooks críticos - ---- - -**Mantenido por:** Core Team | **Origen:** Patrón base para integraciones Stripe diff --git a/shared/libs/payments/_reference/payment.service.reference.ts b/shared/libs/payments/_reference/payment.service.reference.ts deleted file mode 100644 index dd8d0eb34..000000000 --- a/shared/libs/payments/_reference/payment.service.reference.ts +++ /dev/null @@ -1,295 +0,0 @@ -/** - * PAYMENT SERVICE - REFERENCE IMPLEMENTATION - * - * @description Servicio de pagos con integración Stripe. - * Soporta pagos únicos, suscripciones y webhooks. - * - * @usage - * ```typescript - * // Crear checkout session - * const session = await this.paymentService.createCheckoutSession({ - * userId: req.user.id, - * priceId: 'price_xxx', - * successUrl: 'https://app.com/success', - * cancelUrl: 'https://app.com/cancel', - * }); - * // Redirigir a session.url - * ``` - * - * @origin Patrón base para proyectos con pagos - */ - -import { Injectable, Logger, BadRequestException } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import Stripe from 'stripe'; - -// Adaptar imports según proyecto -// import { Payment, Subscription, Customer } from '../entities'; - -@Injectable() -export class PaymentService { - private readonly logger = new Logger(PaymentService.name); - private readonly stripe: Stripe; - - constructor( - private readonly configService: ConfigService, - - @InjectRepository(Payment, 'payments') - private readonly paymentRepo: Repository, - - @InjectRepository(Subscription, 'payments') - private readonly subscriptionRepo: Repository, - - @InjectRepository(Customer, 'payments') - private readonly customerRepo: Repository, - ) { - this.stripe = new Stripe(this.configService.get('STRIPE_SECRET_KEY'), { - apiVersion: '2023-10-16', - }); - } - - /** - * Crear o recuperar customer de Stripe - */ - async getOrCreateCustomer(userId: string, email: string): Promise { - // Buscar customer existente - const existing = await this.customerRepo.findOne({ where: { user_id: userId } }); - if (existing) return existing.stripe_customer_id; - - // Crear en Stripe - const stripeCustomer = await this.stripe.customers.create({ - email, - metadata: { userId }, - }); - - // Guardar en BD - const customer = this.customerRepo.create({ - user_id: userId, - stripe_customer_id: stripeCustomer.id, - email, - }); - await this.customerRepo.save(customer); - - return stripeCustomer.id; - } - - /** - * Crear sesión de checkout - */ - async createCheckoutSession(dto: CreateCheckoutDto): Promise<{ sessionId: string; url: string }> { - const customerId = await this.getOrCreateCustomer(dto.userId, dto.email); - - const session = await this.stripe.checkout.sessions.create({ - customer: customerId, - payment_method_types: ['card'], - line_items: [ - { - price: dto.priceId, - quantity: dto.quantity || 1, - }, - ], - mode: dto.mode || 'payment', - success_url: dto.successUrl, - cancel_url: dto.cancelUrl, - metadata: { - userId: dto.userId, - ...dto.metadata, - }, - }); - - return { - sessionId: session.id, - url: session.url, - }; - } - - /** - * Crear suscripción directa - */ - async createSubscription(dto: CreateSubscriptionDto): Promise { - const customerId = await this.getOrCreateCustomer(dto.userId, dto.email); - - const stripeSubscription = await this.stripe.subscriptions.create({ - customer: customerId, - items: [{ price: dto.priceId }], - payment_behavior: 'default_incomplete', - expand: ['latest_invoice.payment_intent'], - }); - - const subscription = this.subscriptionRepo.create({ - user_id: dto.userId, - stripe_subscription_id: stripeSubscription.id, - stripe_customer_id: customerId, - status: stripeSubscription.status, - current_period_start: new Date(stripeSubscription.current_period_start * 1000), - current_period_end: new Date(stripeSubscription.current_period_end * 1000), - }); - - return this.subscriptionRepo.save(subscription); - } - - /** - * Cancelar suscripción - */ - async cancelSubscription(subscriptionId: string, userId: string): Promise { - const subscription = await this.subscriptionRepo.findOne({ - where: { id: subscriptionId, user_id: userId }, - }); - - if (!subscription) { - throw new BadRequestException('Subscription not found'); - } - - await this.stripe.subscriptions.update(subscription.stripe_subscription_id, { - cancel_at_period_end: true, - }); - - subscription.cancel_at_period_end = true; - await this.subscriptionRepo.save(subscription); - } - - /** - * Obtener suscripción activa del usuario - */ - async getActiveSubscription(userId: string): Promise { - return this.subscriptionRepo.findOne({ - where: { user_id: userId, status: 'active' }, - }); - } - - /** - * Procesar webhook de Stripe - */ - async handleWebhook(signature: string, payload: Buffer): Promise { - const webhookSecret = this.configService.get('STRIPE_WEBHOOK_SECRET'); - - let event: Stripe.Event; - try { - event = this.stripe.webhooks.constructEvent(payload, signature, webhookSecret); - } catch (err) { - this.logger.error(`Webhook signature verification failed: ${err.message}`); - throw new BadRequestException('Invalid webhook signature'); - } - - this.logger.log(`Processing webhook event: ${event.type}`); - - switch (event.type) { - case 'checkout.session.completed': - await this.handleCheckoutCompleted(event.data.object as Stripe.Checkout.Session); - break; - - case 'invoice.paid': - await this.handleInvoicePaid(event.data.object as Stripe.Invoice); - break; - - case 'invoice.payment_failed': - await this.handlePaymentFailed(event.data.object as Stripe.Invoice); - break; - - case 'customer.subscription.updated': - case 'customer.subscription.deleted': - await this.handleSubscriptionUpdate(event.data.object as Stripe.Subscription); - break; - - default: - this.logger.debug(`Unhandled webhook event: ${event.type}`); - } - } - - // ============ WEBHOOK HANDLERS ============ - - private async handleCheckoutCompleted(session: Stripe.Checkout.Session) { - const userId = session.metadata?.userId; - if (!userId) return; - - const payment = this.paymentRepo.create({ - user_id: userId, - stripe_payment_id: session.payment_intent as string, - amount: session.amount_total, - currency: session.currency, - status: 'completed', - }); - - await this.paymentRepo.save(payment); - this.logger.log(`Payment completed for user: ${userId}`); - } - - private async handleInvoicePaid(invoice: Stripe.Invoice) { - const subscriptionId = invoice.subscription as string; - if (!subscriptionId) return; - - await this.subscriptionRepo.update( - { stripe_subscription_id: subscriptionId }, - { status: 'active' }, - ); - } - - private async handlePaymentFailed(invoice: Stripe.Invoice) { - const subscriptionId = invoice.subscription as string; - if (!subscriptionId) return; - - await this.subscriptionRepo.update( - { stripe_subscription_id: subscriptionId }, - { status: 'past_due' }, - ); - } - - private async handleSubscriptionUpdate(stripeSubscription: Stripe.Subscription) { - await this.subscriptionRepo.update( - { stripe_subscription_id: stripeSubscription.id }, - { - status: stripeSubscription.status, - current_period_end: new Date(stripeSubscription.current_period_end * 1000), - cancel_at_period_end: stripeSubscription.cancel_at_period_end, - }, - ); - } -} - -// ============ TIPOS ============ - -interface CreateCheckoutDto { - userId: string; - email: string; - priceId: string; - quantity?: number; - mode?: 'payment' | 'subscription'; - successUrl: string; - cancelUrl: string; - metadata?: Record; -} - -interface CreateSubscriptionDto { - userId: string; - email: string; - priceId: string; -} - -interface Payment { - id: string; - user_id: string; - stripe_payment_id: string; - amount: number; - currency: string; - status: string; -} - -interface Subscription { - id: string; - user_id: string; - stripe_subscription_id: string; - stripe_customer_id: string; - status: string; - current_period_start: Date; - current_period_end: Date; - cancel_at_period_end?: boolean; -} - -interface Customer { - id: string; - user_id: string; - stripe_customer_id: string; - email: string; -} diff --git a/shared/libs/rate-limiting/IMPLEMENTATION.md b/shared/libs/rate-limiting/IMPLEMENTATION.md deleted file mode 100644 index 2194ebc4c..000000000 --- a/shared/libs/rate-limiting/IMPLEMENTATION.md +++ /dev/null @@ -1,401 +0,0 @@ -# Guía de Implementación: Rate Limiting - -**Versión:** 1.0.0 -**Tiempo estimado:** 30 min - 1 hora -**Complejidad:** Baja - ---- - -## Pre-requisitos - -- [ ] Proyecto NestJS existente -- [ ] (Opcional) Redis para producción - ---- - -## Paso 1: Instalar Dependencias - -```bash -npm install @nestjs/throttler - -# Para producción con Redis (opcional) -npm install @nestjs/throttler-storage-redis redis -``` - ---- - -## Paso 2: Configurar Módulo - -### Opción A: Configuración Simple - -```typescript -// src/app.module.ts -import { Module } from '@nestjs/common'; -import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; -import { APP_GUARD } from '@nestjs/core'; - -@Module({ - imports: [ - ThrottlerModule.forRoot([{ - ttl: 60000, // 60 segundos - limit: 100, // 100 requests por minuto - }]), - ], - providers: [ - { - provide: APP_GUARD, - useClass: ThrottlerGuard, - }, - ], -}) -export class AppModule {} -``` - -### Opción B: Configuración con Ambiente - -```typescript -// src/app.module.ts -import { Module } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; -import { APP_GUARD } from '@nestjs/core'; - -@Module({ - imports: [ - ConfigModule.forRoot(), - ThrottlerModule.forRootAsync({ - imports: [ConfigModule], - inject: [ConfigService], - useFactory: (config: ConfigService) => ([{ - ttl: config.get('THROTTLE_TTL', 60000), - limit: config.get('THROTTLE_LIMIT', 100), - }]), - }), - ], - providers: [ - { - provide: APP_GUARD, - useClass: ThrottlerGuard, - }, - ], -}) -export class AppModule {} -``` - ---- - -## Paso 3: Variables de Entorno - -```env -# .env -THROTTLE_TTL=60000 -THROTTLE_LIMIT=100 -``` - ---- - -## Paso 4: Personalizar por Endpoint - -### Limitar endpoint específico - -```typescript -// src/modules/auth/controllers/auth.controller.ts -import { Controller, Post, Body } from '@nestjs/common'; -import { Throttle } from '@nestjs/throttler'; - -@Controller('auth') -export class AuthController { - - // Solo 5 intentos de login por minuto - @Throttle({ default: { limit: 5, ttl: 60000 } }) - @Post('login') - async login(@Body() dto: LoginDto) { - return this.authService.login(dto); - } - - // Solo 3 registros por hora (anti-spam) - @Throttle({ default: { limit: 3, ttl: 3600000 } }) - @Post('register') - async register(@Body() dto: RegisterDto) { - return this.authService.register(dto); - } -} -``` - -### Excluir endpoint del rate limiting - -```typescript -import { SkipThrottle } from '@nestjs/throttler'; - -@Controller('health') -export class HealthController { - @SkipThrottle() - @Get() - check() { - return { status: 'ok' }; - } -} -``` - ---- - -## Paso 5: Rate Limiting por Usuario (Opcional) - -```typescript -// src/common/guards/custom-throttler.guard.ts -import { Injectable, ExecutionContext } from '@nestjs/common'; -import { ThrottlerGuard } from '@nestjs/throttler'; - -@Injectable() -export class CustomThrottlerGuard extends ThrottlerGuard { - protected async getTracker(req: Record): Promise { - // Si hay usuario autenticado, limitar por usuario - if (req.user?.id) { - return `user-${req.user.id}`; - } - // Si no, limitar por IP - return req.ip; - } - - // Opcional: Personalizar mensaje de error - protected throwThrottlingException(): void { - throw new HttpException( - { - statusCode: 429, - message: 'Demasiadas solicitudes. Intente de nuevo más tarde.', - error: 'Too Many Requests', - }, - HttpStatus.TOO_MANY_REQUESTS, - ); - } -} -``` - -Registrar en app.module.ts: - -```typescript -providers: [ - { - provide: APP_GUARD, - useClass: CustomThrottlerGuard, // En lugar de ThrottlerGuard - }, -], -``` - ---- - -## Paso 6: Múltiples Límites (Opcional) - -```typescript -// src/app.module.ts -ThrottlerModule.forRoot([ - { - name: 'short', - ttl: 1000, // 1 segundo - limit: 3, // Máximo 3 por segundo (anti-DDoS básico) - }, - { - name: 'medium', - ttl: 10000, // 10 segundos - limit: 20, // Máximo 20 por 10 segundos - }, - { - name: 'long', - ttl: 60000, // 1 minuto - limit: 100, // Máximo 100 por minuto - }, -]) -``` - -Usar en endpoint: - -```typescript -@Throttle({ - short: { limit: 1, ttl: 1000 }, - long: { limit: 10, ttl: 60000 }, -}) -@Post('heavy-operation') -async heavyOperation() { - // ... -} -``` - ---- - -## Paso 7: Redis para Producción (Opcional) - -```bash -npm install @nestjs/throttler-storage-redis ioredis -``` - -```typescript -// src/app.module.ts -import { ThrottlerStorageRedisService } from '@nestjs/throttler-storage-redis'; -import Redis from 'ioredis'; - -ThrottlerModule.forRootAsync({ - imports: [ConfigModule], - inject: [ConfigService], - useFactory: (config: ConfigService) => ({ - throttlers: [{ - ttl: config.get('THROTTLE_TTL', 60000), - limit: config.get('THROTTLE_LIMIT', 100), - }], - storage: new ThrottlerStorageRedisService( - new Redis({ - host: config.get('REDIS_HOST', 'localhost'), - port: config.get('REDIS_PORT', 6379), - password: config.get('REDIS_PASSWORD'), - }), - ), - }), -}) -``` - -Variables de entorno adicionales: - -```env -REDIS_HOST=localhost -REDIS_PORT=6379 -REDIS_PASSWORD= -``` - ---- - -## Paso 8: Logging de Rate Limits (Opcional) - -```typescript -// src/common/guards/throttler-logger.guard.ts -import { Injectable, ExecutionContext, Logger } from '@nestjs/common'; -import { ThrottlerGuard } from '@nestjs/throttler'; - -@Injectable() -export class ThrottlerLoggerGuard extends ThrottlerGuard { - private readonly logger = new Logger('RateLimit'); - - protected async handleRequest( - context: ExecutionContext, - limit: number, - ttl: number, - throttler: any, - getTracker: any, - generateKey: any, - ): Promise { - const result = await super.handleRequest( - context, - limit, - ttl, - throttler, - getTracker, - generateKey, - ); - - if (!result) { - const req = context.switchToHttp().getRequest(); - this.logger.warn( - `Rate limit exceeded: ${req.ip} - ${req.method} ${req.url}`, - ); - } - - return result; - } -} -``` - ---- - -## Configuraciones Recomendadas - -### Desarrollo - -```typescript -ThrottlerModule.forRoot([{ - ttl: 60000, - limit: 1000, // Alto para no molestar durante desarrollo -}]) -``` - -### Producción - -```typescript -ThrottlerModule.forRoot([ - { name: 'short', ttl: 1000, limit: 10 }, - { name: 'long', ttl: 60000, limit: 100 }, -]) -``` - -### Por tipo de endpoint - -| Tipo | Límite Recomendado | -|------|-------------------| -| Login | 5 por minuto | -| Registro | 3 por hora | -| API pública | 100 por minuto | -| API autenticada | 1000 por minuto | -| Operaciones costosas | 10 por minuto | -| Webhooks | Sin límite (SkipThrottle) | -| Health check | Sin límite (SkipThrottle) | - ---- - -## Checklist de Implementación - -- [ ] @nestjs/throttler instalado -- [ ] ThrottlerModule configurado en AppModule -- [ ] ThrottlerGuard registrado como APP_GUARD -- [ ] Variables de entorno configuradas -- [ ] Límites ajustados para endpoints críticos (login, register) -- [ ] Endpoints públicos excluidos (health, webhooks) -- [ ] Redis configurado (producción) -- [ ] Build pasa sin errores -- [ ] Test manual: verificar respuesta 429 - ---- - -## Verificar Funcionamiento - -```bash -# Test simple con curl -for i in {1..10}; do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/test; done - -# Debería mostrar 200 hasta el límite, luego 429 -``` - ---- - -## Troubleshooting - -### Error: "ThrottlerModule is not imported" -Asegurarse de importar `ThrottlerModule.forRoot()` en AppModule. - -### Rate limiting no funciona -Verificar que `ThrottlerGuard` esté registrado como `APP_GUARD`. - -### 429 en todos los requests -El límite es muy bajo. Aumentar `limit` en configuración. - -### Memory leaks en producción -Usar Redis como storage en lugar de memoria. - ---- - -## Código de Referencia - -Estructura típica: - -``` -src/ -├── app.module.ts # ThrottlerModule configurado -├── common/ -│ └── guards/ -│ └── custom-throttler.guard.ts # Guard personalizado -└── modules/ - └── auth/ - └── controllers/ - └── auth.controller.ts # @Throttle en endpoints -``` - ---- - -**Versión:** 1.0.0 -**Sistema:** SIMCO Catálogo diff --git a/shared/libs/rate-limiting/README.md b/shared/libs/rate-limiting/README.md deleted file mode 100644 index 56341abdf..000000000 --- a/shared/libs/rate-limiting/README.md +++ /dev/null @@ -1,354 +0,0 @@ -# Limitación de Tasa (Rate Limiting) - -**Versión:** 1.0.0 -**Origen:** projects/gamilit -**Estado:** Producción -**Última actualización:** 2025-12-08 - ---- - -## Descripción - -Sistema de limitación de tasa para proteger APIs contra abuso: -- Rate limiting por IP/usuario -- Configuración por endpoint -- Headers HTTP estándar -- Respuestas 429 con Retry-After - ---- - -## Características - -| Característica | Descripción | -|----------------|-------------| -| Por IP | Limita requests por dirección IP | -| Por Usuario | Limita requests por usuario autenticado | -| Por Endpoint | Configuración individual por ruta | -| Global | Límite base para toda la aplicación | -| Headers Estándar | X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After | - ---- - -## Stack Tecnológico - -```yaml -backend: - framework: NestJS - library: "@nestjs/throttler" - storage: "Memory (default) | Redis (producción)" -``` - ---- - -## Dependencias NPM - -```json -{ - "@nestjs/throttler": "^5.x" -} -``` - -Para producción con Redis: -```json -{ - "@nestjs/throttler-storage-redis": "^0.x", - "redis": "^4.x" -} -``` - ---- - -## Configuración Básica - -### En módulo principal - -```typescript -import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; -import { APP_GUARD } from '@nestjs/core'; - -@Module({ - imports: [ - ThrottlerModule.forRoot([{ - ttl: 60000, // 60 segundos - limit: 100, // 100 requests por TTL - }]), - ], - providers: [ - { - provide: APP_GUARD, - useClass: ThrottlerGuard, - }, - ], -}) -export class AppModule {} -``` - -### Configuración por ambiente - -```typescript -ThrottlerModule.forRootAsync({ - imports: [ConfigModule], - inject: [ConfigService], - useFactory: (config: ConfigService) => ([{ - ttl: config.get('THROTTLE_TTL', 60000), - limit: config.get('THROTTLE_LIMIT', 100), - }]), -}) -``` - ---- - -## Uso por Endpoint - -### Sobrescribir límites - -```typescript -import { Throttle, SkipThrottle } from '@nestjs/throttler'; - -@Controller('auth') -export class AuthController { - - // Más estricto: 5 intentos por minuto - @Throttle({ default: { limit: 5, ttl: 60000 } }) - @Post('login') - login() {} - - // Omitir rate limiting - @SkipThrottle() - @Get('public') - publicEndpoint() {} -} -``` - -### En controlador completo - -```typescript -@Controller('api') -@Throttle({ default: { limit: 50, ttl: 60000 } }) -export class ApiController { - // Todos los endpoints heredan el límite -} -``` - ---- - -## Múltiples Límites - -```typescript -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 - }, -]) -``` - -Usar en endpoint: - -```typescript -@Throttle({ - short: { limit: 1, ttl: 1000 }, - long: { limit: 10, ttl: 60000 }, -}) -@Post('expensive-operation') -expensiveOperation() {} -``` - ---- - -## Rate Limiting por Usuario - -### Custom ThrottlerGuard - -```typescript -import { Injectable, ExecutionContext } from '@nestjs/common'; -import { ThrottlerGuard } from '@nestjs/throttler'; - -@Injectable() -export class CustomThrottlerGuard extends ThrottlerGuard { - protected async getTracker(req: Record): Promise { - // Si hay usuario autenticado, usar su ID - if (req.user?.id) { - return req.user.id; - } - // Fallback a IP - return req.ip; - } -} -``` - -Registrar: - -```typescript -providers: [ - { - provide: APP_GUARD, - useClass: CustomThrottlerGuard, - }, -], -``` - ---- - -## Almacenamiento Redis (Producción) - -```typescript -import { ThrottlerStorageRedisService } from '@nestjs/throttler-storage-redis'; -import Redis from 'ioredis'; - -ThrottlerModule.forRootAsync({ - imports: [ConfigModule], - inject: [ConfigService], - useFactory: (config: ConfigService) => ({ - throttlers: [{ - ttl: config.get('THROTTLE_TTL', 60000), - limit: config.get('THROTTLE_LIMIT', 100), - }], - storage: new ThrottlerStorageRedisService(new Redis({ - host: config.get('REDIS_HOST', 'localhost'), - port: config.get('REDIS_PORT', 6379), - })), - }), -}) -``` - ---- - -## Headers de Respuesta - -El módulo agrega automáticamente: - -``` -X-RateLimit-Limit: 100 # Límite máximo -X-RateLimit-Remaining: 95 # Requests restantes -X-RateLimit-Reset: 1234567890 # Timestamp de reset -``` - -En respuesta 429: - -``` -Retry-After: 60 # Segundos hasta poder reintentar -``` - ---- - -## Excepciones Personalizadas - -```typescript -import { ThrottlerException } from '@nestjs/throttler'; - -// En el guard personalizado -protected throwThrottlingException(): void { - throw new ThrottlerException( - 'Demasiadas solicitudes. Por favor, espere un momento.', - ); -} -``` - ---- - -## Variables de Entorno - -```env -# Configuración básica -THROTTLE_TTL=60000 # Ventana de tiempo en ms -THROTTLE_LIMIT=100 # Requests por ventana - -# Redis (producción) -REDIS_HOST=localhost -REDIS_PORT=6379 -``` - ---- - -## Casos de Uso Comunes - -### 1. Login (anti brute-force) - -```typescript -@Throttle({ default: { limit: 5, ttl: 300000 } }) // 5 por 5 minutos -@Post('login') -login() {} -``` - -### 2. Registro (anti spam) - -```typescript -@Throttle({ default: { limit: 3, ttl: 3600000 } }) // 3 por hora -@Post('register') -register() {} -``` - -### 3. API pública - -```typescript -@Throttle({ default: { limit: 1000, ttl: 3600000 } }) // 1000 por hora -@Get('public-data') -getData() {} -``` - -### 4. Endpoints costosos - -```typescript -@Throttle({ default: { limit: 10, ttl: 60000 } }) // 10 por minuto -@Post('generate-report') -generateReport() {} -``` - ---- - -## Métricas y Logging - -```typescript -@Injectable() -export class ThrottlerLoggerGuard extends ThrottlerGuard { - protected async handleRequest( - context: ExecutionContext, - limit: number, - ttl: number, - ): Promise { - const req = context.switchToHttp().getRequest(); - const tracker = await this.getTracker(req); - - const result = await super.handleRequest(context, limit, ttl); - - if (!result) { - this.logger.warn(`Rate limit exceeded for: ${tracker}`); - } - - return result; - } -} -``` - ---- - -## Adaptaciones Necesarias - -1. **Límites**: Ajustar según tráfico esperado -2. **Storage**: Memory para desarrollo, Redis para producción -3. **Tracker**: IP vs Usuario según necesidad -4. **Mensajes**: Personalizar respuestas 429 - ---- - -## Referencias - -- [NestJS Throttler](https://docs.nestjs.com/security/rate-limiting) -- [RFC 6585](https://tools.ietf.org/html/rfc6585#section-4) (429 Too Many Requests) - ---- - -**Mantenido por:** Sistema NEXUS -**Proyecto origen:** Gamilit Platform diff --git a/shared/libs/rate-limiting/_reference/rate-limiter.service.reference.ts b/shared/libs/rate-limiting/_reference/rate-limiter.service.reference.ts deleted file mode 100644 index 6baef22c3..000000000 --- a/shared/libs/rate-limiting/_reference/rate-limiter.service.reference.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * RATE LIMITER SERVICE - REFERENCE IMPLEMENTATION - * - * @description Servicio de rate limiting para proteger endpoints. - * Implementación in-memory simple con soporte para diferentes estrategias. - * - * @usage - * ```typescript - * // En middleware o guard - * const limiter = getRateLimiter({ windowMs: 60000, max: 100 }); - * if (limiter.isRateLimited(req.ip)) { - * throw new TooManyRequestsException(); - * } - * ``` - * - * @origin gamilit/apps/backend/src/shared/services/rate-limiter.service.ts - */ - -/** - * Configuración del rate limiter - */ -export interface RateLimitConfig { - /** Ventana de tiempo en milisegundos */ - windowMs: number; - /** Máximo de requests por ventana */ - max: number; - /** Mensaje de error personalizado */ - message?: string; - /** Función para generar key (default: IP) */ - keyGenerator?: (req: any) => string; -} - -/** - * Estado interno de un cliente - */ -interface ClientState { - count: number; - resetTime: number; -} - -/** - * Rate Limiter Factory - * - * @param config - Configuración del limiter - * @returns Instancia del rate limiter - */ -export function getRateLimiter(config: RateLimitConfig): RateLimiter { - const clients = new Map(); - - // Limpieza periódica de entradas expiradas - const cleanup = setInterval(() => { - const now = Date.now(); - for (const [key, state] of clients.entries()) { - if (state.resetTime <= now) { - clients.delete(key); - } - } - }, config.windowMs); - - return { - /** - * Verificar si un cliente está rate limited - */ - check(key: string): RateLimitResult { - const now = Date.now(); - const state = clients.get(key); - - // Cliente nuevo o ventana expirada - if (!state || state.resetTime <= now) { - clients.set(key, { - count: 1, - resetTime: now + config.windowMs, - }); - return { - limited: false, - remaining: config.max - 1, - resetTime: now + config.windowMs, - }; - } - - // Incrementar contador - state.count++; - - // Verificar límite - if (state.count > config.max) { - return { - limited: true, - remaining: 0, - resetTime: state.resetTime, - retryAfter: Math.ceil((state.resetTime - now) / 1000), - }; - } - - return { - limited: false, - remaining: config.max - state.count, - resetTime: state.resetTime, - }; - }, - - /** - * Resetear contador de un cliente - */ - reset(key: string): void { - clients.delete(key); - }, - - /** - * Limpiar todos los contadores - */ - clear(): void { - clients.clear(); - }, - - /** - * Destruir el limiter (detener cleanup) - */ - destroy(): void { - clearInterval(cleanup); - clients.clear(); - }, - }; -} - -/** - * Interfaz del rate limiter - */ -export interface RateLimiter { - check(key: string): RateLimitResult; - reset(key: string): void; - clear(): void; - destroy(): void; -} - -/** - * Resultado de verificación - */ -export interface RateLimitResult { - limited: boolean; - remaining: number; - resetTime: number; - retryAfter?: number; -} - -/** - * Middleware para Express/NestJS - */ -export function rateLimitMiddleware(config: RateLimitConfig) { - const limiter = getRateLimiter(config); - const keyGenerator = config.keyGenerator || ((req) => req.ip || 'unknown'); - - return (req: any, res: any, next: () => void) => { - const key = keyGenerator(req); - const result = limiter.check(key); - - // Agregar headers informativos - res.setHeader('X-RateLimit-Limit', config.max); - res.setHeader('X-RateLimit-Remaining', result.remaining); - res.setHeader('X-RateLimit-Reset', result.resetTime); - - if (result.limited) { - res.setHeader('Retry-After', result.retryAfter); - res.status(429).json({ - statusCode: 429, - message: config.message || 'Too many requests', - retryAfter: result.retryAfter, - }); - return; - } - - next(); - }; -} - -/** - * Error de rate limit - */ -export class TooManyRequestsError extends Error { - public readonly statusCode = 429; - public readonly retryAfter: number; - - constructor(retryAfter: number, message?: string) { - super(message || 'Too many requests'); - this.retryAfter = retryAfter; - } -} diff --git a/shared/libs/session-management/IMPLEMENTATION.md b/shared/libs/session-management/IMPLEMENTATION.md deleted file mode 100644 index af3810662..000000000 --- a/shared/libs/session-management/IMPLEMENTATION.md +++ /dev/null @@ -1,541 +0,0 @@ -# Guía de Implementación: Gestión de Sesiones - -**Versión:** 1.0.0 -**Tiempo estimado:** 1-2 horas (adaptación) -**Complejidad:** Media - ---- - -## Pre-requisitos - -- [ ] NestJS con TypeORM configurado -- [ ] Tabla `user_sessions` creada -- [ ] Módulo de autenticación existente - ---- - -## Paso 1: Crear DDL - -```sql --- Schema auth_management (si no existe) -CREATE SCHEMA IF NOT EXISTS auth_management; - --- Tabla de sesiones -CREATE TABLE auth_management.user_sessions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - tenant_id UUID, - session_token TEXT NOT NULL UNIQUE, - refresh_token TEXT, - user_agent TEXT, - ip_address INET, - device_type VARCHAR(50) CHECK (device_type IN ('desktop', 'mobile', 'tablet', 'unknown')), - browser VARCHAR(100), - os VARCHAR(100), - country VARCHAR(100), - city VARCHAR(100), - created_at TIMESTAMPTZ DEFAULT NOW(), - last_activity_at TIMESTAMPTZ DEFAULT NOW(), - expires_at TIMESTAMPTZ NOT NULL, - is_active BOOLEAN DEFAULT TRUE, - revoked_at TIMESTAMPTZ, - metadata JSONB DEFAULT '{}', - - CONSTRAINT valid_expires CHECK (expires_at > created_at) -); - --- Índices para performance -CREATE INDEX idx_user_sessions_user_id ON auth_management.user_sessions(user_id); -CREATE INDEX idx_user_sessions_tenant_id ON auth_management.user_sessions(tenant_id); -CREATE INDEX idx_user_sessions_session_token ON auth_management.user_sessions(session_token); -CREATE INDEX idx_user_sessions_expires_at ON auth_management.user_sessions(expires_at); -CREATE INDEX idx_user_sessions_is_active ON auth_management.user_sessions(is_active); - --- Comentarios -COMMENT ON TABLE auth_management.user_sessions IS 'Sesiones activas de usuarios con tracking de dispositivo'; -COMMENT ON COLUMN auth_management.user_sessions.refresh_token IS 'Token hasheado con SHA256, nunca texto plano'; -COMMENT ON COLUMN auth_management.user_sessions.device_type IS 'Tipo de dispositivo detectado: desktop, mobile, tablet, unknown'; -``` - ---- - -## Paso 2: Crear Entity - -```typescript -// src/modules/auth/entities/user-session.entity.ts -import { - Entity, - PrimaryGeneratedColumn, - Column, - ManyToOne, - JoinColumn, - Index, -} from 'typeorm'; -import { Exclude } from 'class-transformer'; - -@Entity({ schema: 'auth_management', name: 'user_sessions' }) -@Index(['user_id']) -@Index(['session_token']) -@Index(['expires_at']) -export class UserSession { - @PrimaryGeneratedColumn('uuid') - id!: string; - - @Column({ type: 'uuid' }) - user_id!: string; - - @Column({ type: 'uuid', nullable: true }) - tenant_id?: string; - - @Column({ type: 'text', unique: true }) - session_token!: string; - - @Column({ type: 'text', nullable: true }) - @Exclude() // IMPORTANTE: No serializar en respuestas - refresh_token?: string; - - @Column({ type: 'text', nullable: true }) - user_agent?: string; - - @Column({ type: 'inet', nullable: true }) - ip_address?: string; - - @Column({ type: 'varchar', length: 50, nullable: true }) - device_type?: string; - - @Column({ type: 'varchar', length: 100, nullable: true }) - browser?: string; - - @Column({ type: 'varchar', length: 100, nullable: true }) - os?: string; - - @Column({ type: 'varchar', length: 100, nullable: true }) - country?: string; - - @Column({ type: 'varchar', length: 100, nullable: true }) - city?: string; - - @Column({ type: 'timestamp with time zone', default: () => 'NOW()' }) - created_at!: Date; - - @Column({ type: 'timestamp with time zone', default: () => 'NOW()' }) - last_activity_at!: Date; - - @Column({ type: 'timestamp with time zone' }) - expires_at!: Date; - - @Column({ type: 'boolean', default: true }) - is_active!: boolean; - - @Column({ type: 'timestamp with time zone', nullable: true }) - revoked_at?: Date; - - @Column({ type: 'jsonb', default: {} }) - metadata!: Record; -} -``` - ---- - -## Paso 3: Crear DTOs - -```typescript -// src/modules/auth/dto/create-user-session.dto.ts -import { IsString, IsUUID, IsOptional, IsDateString } from 'class-validator'; - -export class CreateUserSessionDto { - @IsUUID() - user_id!: string; - - @IsUUID() - @IsOptional() - tenant_id?: string; - - @IsString() - session_token!: string; - - @IsString() - @IsOptional() - refresh_token?: string; - - @IsString() - @IsOptional() - user_agent?: string; - - @IsString() - @IsOptional() - ip_address?: string; - - @IsString() - @IsOptional() - device_type?: string; - - @IsString() - @IsOptional() - browser?: string; - - @IsString() - @IsOptional() - os?: string; - - @IsDateString() - expires_at!: string; -} - -// src/modules/auth/dto/user-session-response.dto.ts -export class UserSessionResponseDto { - id!: string; - device_type?: string; - browser?: string; - os?: string; - ip_address?: string; - country?: string; - city?: string; - created_at!: Date; - last_activity_at!: Date; - is_current?: boolean; // Calculado en runtime -} -``` - ---- - -## Paso 4: Crear Service - -```typescript -// src/modules/auth/services/session-management.service.ts -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, LessThan } from 'typeorm'; -import * as crypto from 'crypto'; -import { UserSession } from '../entities/user-session.entity'; -import { CreateUserSessionDto } from '../dto/create-user-session.dto'; - -@Injectable() -export class SessionManagementService { - private readonly MAX_SESSIONS_PER_USER = 5; - - constructor( - @InjectRepository(UserSession) - private readonly sessionRepository: Repository, - ) {} - - /** - * Crear nueva sesión - * - Limpia sesiones expiradas - * - Si hay 5+, elimina la más antigua - * - Hashea el refresh token - */ - async createSession(dto: CreateUserSessionDto): Promise { - // Limpiar sesiones expiradas - await this.deleteExpiredSessions(dto.user_id); - - // Verificar límite - const count = await this.countActiveSessions(dto.user_id); - if (count >= this.MAX_SESSIONS_PER_USER) { - await this.deleteOldestSession(dto.user_id); - } - - // Hashear refresh token - const hashedRefreshToken = dto.refresh_token - ? this.hashToken(dto.refresh_token) - : null; - - // Crear sesión - const session = this.sessionRepository.create({ - ...dto, - refresh_token: hashedRefreshToken, - expires_at: new Date(dto.expires_at), - }); - - return this.sessionRepository.save(session); - } - - /** - * Validar sesión y actualizar actividad - */ - async validateSession(sessionId: string): Promise { - const session = await this.sessionRepository.findOne({ - where: { id: sessionId }, - }); - - if (!session) return null; - - // Validar expiración - if (new Date() > session.expires_at) { - await this.sessionRepository.delete({ id: sessionId }); - return null; - } - - // Actualizar última actividad - session.last_activity_at = new Date(); - await this.sessionRepository.save(session); - - return session; - } - - /** - * Renovar sesión - */ - async refreshSession(sessionId: string, newExpiresAt: Date): Promise { - const session = await this.validateSession(sessionId); - - if (!session) { - throw new NotFoundException('Sesión no encontrada o expirada'); - } - - session.expires_at = newExpiresAt; - session.last_activity_at = new Date(); - - return this.sessionRepository.save(session); - } - - /** - * Revocar sesión específica (con validación de ownership) - */ - async revokeSession(sessionId: string, userId: string): Promise<{ message: string }> { - const session = await this.sessionRepository.findOne({ - where: { id: sessionId, user_id: userId }, // Validación de ownership - }); - - if (!session) { - throw new NotFoundException('Sesión no encontrada'); - } - - session.is_active = false; - session.revoked_at = new Date(); - await this.sessionRepository.save(session); - - return { message: 'Sesión cerrada correctamente' }; - } - - /** - * Revocar todas las sesiones excepto la actual - */ - async revokeAllSessions( - userId: string, - currentSessionId: string, - ): Promise<{ message: string; count: number }> { - const sessions = await this.sessionRepository.find({ - where: { user_id: userId, is_active: true }, - }); - - const toRevoke = sessions.filter((s) => s.id !== currentSessionId); - const now = new Date(); - - for (const session of toRevoke) { - session.is_active = false; - session.revoked_at = now; - } - - await this.sessionRepository.save(toRevoke); - - return { - message: 'Sesiones cerradas correctamente', - count: toRevoke.length, - }; - } - - /** - * Limpiar sesiones expiradas (para cron job) - */ - async cleanExpiredSessions(): Promise { - const result = await this.sessionRepository.delete({ - expires_at: LessThan(new Date()), - }); - return result.affected || 0; - } - - /** - * Obtener sesiones activas del usuario - */ - async getSessions(userId: string): Promise { - return this.sessionRepository.find({ - where: { user_id: userId, is_active: true }, - order: { last_activity_at: 'DESC' }, - select: [ - 'id', - 'device_type', - 'browser', - 'os', - 'ip_address', - 'country', - 'city', - 'created_at', - 'last_activity_at', - ], - }); - } - - /** - * Buscar sesión por refresh token hasheado - */ - async findByRefreshToken( - userId: string, - refreshToken: string, - ): Promise { - const hashedToken = this.hashToken(refreshToken); - return this.sessionRepository.findOne({ - where: { - user_id: userId, - refresh_token: hashedToken, - is_active: true, - }, - }); - } - - // === Helpers privados === - - private async countActiveSessions(userId: string): Promise { - return this.sessionRepository.count({ - where: { user_id: userId, is_active: true }, - }); - } - - private async deleteOldestSession(userId: string): Promise { - const oldest = await this.sessionRepository.findOne({ - where: { user_id: userId }, - order: { created_at: 'ASC' }, - }); - - if (oldest) { - await this.sessionRepository.delete({ id: oldest.id }); - } - } - - private async deleteExpiredSessions(userId: string): Promise { - await this.sessionRepository.delete({ - user_id: userId, - expires_at: LessThan(new Date()), - }); - } - - private hashToken(token: string): string { - return crypto.createHash('sha256').update(token).digest('hex'); - } -} -``` - ---- - -## Paso 5: Registrar en Módulo - -```typescript -// src/modules/auth/auth.module.ts -import { SessionManagementService } from './services/session-management.service'; -import { UserSession } from './entities/user-session.entity'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([User, UserSession]), - // ... - ], - providers: [ - AuthService, - SessionManagementService, // Agregar - JwtStrategy, - ], - exports: [ - AuthService, - SessionManagementService, // Exportar si se usa en otros módulos - ], -}) -export class AuthModule {} -``` - ---- - -## Paso 6: Agregar Endpoints - -```typescript -// src/modules/auth/controllers/users.controller.ts -import { Controller, Get, Delete, Post, Param, Body, UseGuards, Request } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { JwtAuthGuard } from '../guards/jwt-auth.guard'; -import { SessionManagementService } from '../services/session-management.service'; - -@ApiTags('Users') -@Controller('users') -@UseGuards(JwtAuthGuard) -@ApiBearerAuth() -export class UsersController { - constructor( - private readonly sessionService: SessionManagementService, - ) {} - - @Get('sessions') - async getSessions(@Request() req) { - return this.sessionService.getSessions(req.user.id); - } - - @Delete('sessions/:id') - async revokeSession(@Param('id') sessionId: string, @Request() req) { - return this.sessionService.revokeSession(sessionId, req.user.id); - } - - @Post('sessions/revoke-all') - async revokeAllSessions( - @Request() req, - @Body() body: { currentSessionId: string }, - ) { - return this.sessionService.revokeAllSessions(req.user.id, body.currentSessionId); - } -} -``` - ---- - -## Paso 7: Configurar Cron Job (Opcional) - -```typescript -// src/modules/tasks/tasks.service.ts -import { Injectable, Logger } from '@nestjs/common'; -import { Cron, CronExpression } from '@nestjs/schedule'; -import { SessionManagementService } from '@/modules/auth/services/session-management.service'; - -@Injectable() -export class TasksService { - private readonly logger = new Logger(TasksService.name); - - constructor( - private readonly sessionService: SessionManagementService, - ) {} - - @Cron(CronExpression.EVERY_HOUR) - async cleanExpiredSessions() { - const count = await this.sessionService.cleanExpiredSessions(); - this.logger.log(`Limpiadas ${count} sesiones expiradas`); - } -} -``` - -```bash -# Instalar @nestjs/schedule si no está instalado -npm install @nestjs/schedule -``` - ---- - -## Checklist de Implementación - -- [ ] DDL creado y ejecutado -- [ ] Entity alineada con DDL -- [ ] DTOs con validaciones -- [ ] Service con todos los métodos -- [ ] Service registrado en módulo -- [ ] Endpoints agregados al controller -- [ ] Cron job configurado (opcional) -- [ ] Build pasa sin errores -- [ ] Tests básicos funcionando - ---- - -## Código de Referencia - -Ver implementación completa en: -- `projects/gamilit/apps/backend/src/modules/auth/services/session-management.service.ts` -- `projects/gamilit/apps/backend/src/modules/auth/entities/user-session.entity.ts` - ---- - -**Versión:** 1.0.0 -**Sistema:** SIMCO Catálogo diff --git a/shared/libs/session-management/README.md b/shared/libs/session-management/README.md deleted file mode 100644 index 5eb08f32b..000000000 --- a/shared/libs/session-management/README.md +++ /dev/null @@ -1,301 +0,0 @@ -# Gestión de Sesiones - -**Versión:** 1.0.0 -**Origen:** projects/gamilit -**Estado:** Producción -**Última actualización:** 2025-12-08 - ---- - -## Descripción - -Sistema de gestión de sesiones de usuario con: -- Múltiples sesiones concurrentes (máx 5) -- Tracking de dispositivo, IP, ubicación -- Renovación automática con refresh tokens -- Revocación individual y masiva -- Limpieza automática de sesiones expiradas - ---- - -## Características - -| Característica | Descripción | -|----------------|-------------| -| Multi-sesión | Hasta 5 sesiones concurrentes por usuario | -| Auto-limpieza | Sesiones más antiguas eliminadas automáticamente | -| Device Tracking | IP, User-Agent, dispositivo, navegador, OS | -| Geo-location | País y ciudad (si disponible) | -| Refresh Tokens | Hasheados con SHA256 | -| Revocación | Individual o masiva | -| Multi-tenant | Soporte para múltiples tenants | - ---- - -## Stack Tecnológico - -```yaml -backend: - framework: NestJS - orm: TypeORM - crypto: Node.js crypto (SHA256) - -database: - engine: PostgreSQL - schemas: - - auth_management (sesiones) -``` - ---- - -## Dependencias NPM - -```json -{ - "typeorm": "^0.3.x", - "@nestjs/typeorm": "^10.x" -} -``` - -Nota: crypto es nativo de Node.js, no requiere instalación. - ---- - -## Tabla Requerida - -```sql -CREATE TABLE auth_management.user_sessions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - tenant_id UUID, - session_token TEXT NOT NULL UNIQUE, - refresh_token TEXT, -- Hasheado con SHA256 - user_agent TEXT, - ip_address INET, - device_type VARCHAR(50), -- desktop, mobile, tablet, unknown - browser VARCHAR(100), - os VARCHAR(100), - country VARCHAR(100), - city VARCHAR(100), - created_at TIMESTAMPTZ DEFAULT NOW(), - last_activity_at TIMESTAMPTZ DEFAULT NOW(), - expires_at TIMESTAMPTZ NOT NULL, - is_active BOOLEAN DEFAULT TRUE, - revoked_at TIMESTAMPTZ, - metadata JSONB DEFAULT '{}' -); - --- Índices -CREATE INDEX idx_user_sessions_user_id ON auth_management.user_sessions(user_id); -CREATE INDEX idx_user_sessions_tenant_id ON auth_management.user_sessions(tenant_id); -CREATE INDEX idx_user_sessions_session_token ON auth_management.user_sessions(session_token); -CREATE INDEX idx_user_sessions_expires_at ON auth_management.user_sessions(expires_at); -``` - ---- - -## Estructura del Módulo - -``` -session-management/ -├── services/ -│ └── session-management.service.ts -├── entities/ -│ └── user-session.entity.ts -├── dto/ -│ ├── create-user-session.dto.ts -│ ├── update-user-session.dto.ts -│ └── user-session-response.dto.ts -└── __tests__/ - └── session-management.service.spec.ts -``` - ---- - -## API del Servicio - -```typescript -class SessionManagementService { - // Crear nueva sesión - async createSession(dto: CreateUserSessionDto): Promise; - - // Validar sesión y actualizar actividad - async validateSession(sessionId: string): Promise; - - // Renovar sesión - async refreshSession(sessionId: string, newExpiresAt: Date): Promise; - - // Revocar sesión específica - async revokeSession(sessionId: string, userId: string): Promise<{ message: string }>; - - // Revocar todas excepto la actual - async revokeAllSessions(userId: string, currentSessionId: string): Promise<{ message: string; count: number }>; - - // Limpiar sesiones expiradas (cron) - async cleanExpiredSessions(): Promise; - - // Obtener sesiones activas del usuario - async getSessions(userId: string): Promise; -} -``` - ---- - -## Uso Rápido - -### 1. Crear sesión al login - -```typescript -import { SessionManagementService } from '@/modules/auth/services'; - -// En AuthService.login() -const session = await this.sessionManagementService.createSession({ - user_id: user.id, - tenant_id: profile.tenant_id, - session_token: crypto.randomBytes(32).toString('hex'), - refresh_token: refreshToken, // Será hasheado internamente - ip_address: req.ip, - user_agent: req.headers['user-agent'], - device_type: this.detectDeviceType(userAgent), - browser: this.detectBrowser(userAgent), - os: this.detectOS(userAgent), - expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 días -}); -``` - -### 2. Obtener sesiones activas - -```typescript -// En UsersController -@Get('sessions') -@UseGuards(JwtAuthGuard) -async getSessions(@Request() req) { - return this.sessionManagementService.getSessions(req.user.id); -} -``` - -### 3. Revocar sesión - -```typescript -// En UsersController -@Delete('sessions/:id') -@UseGuards(JwtAuthGuard) -async revokeSession(@Param('id') sessionId: string, @Request() req) { - return this.sessionManagementService.revokeSession(sessionId, req.user.id); -} -``` - -### 4. Cerrar todas las sesiones - -```typescript -// En UsersController -@Post('sessions/revoke-all') -@UseGuards(JwtAuthGuard) -async revokeAllSessions(@Request() req, @Body() body: { currentSessionId: string }) { - return this.sessionManagementService.revokeAllSessions( - req.user.id, - body.currentSessionId - ); -} -``` - -### 5. Cron job para limpieza - -```typescript -import { Cron, CronExpression } from '@nestjs/schedule'; - -@Cron(CronExpression.EVERY_HOUR) -async cleanExpiredSessions() { - const count = await this.sessionManagementService.cleanExpiredSessions(); - this.logger.log(`Limpiadas ${count} sesiones expiradas`); -} -``` - ---- - -## Comportamientos Clave - -### Límite de Sesiones - -```typescript -MAX_SESSIONS_PER_USER = 5; - -// Al crear la 6ta sesión: -// 1. Se eliminan sesiones expiradas -// 2. Si aún hay 5+, se elimina la más antigua -// 3. Se crea la nueva sesión -``` - -### Hasheo de Refresh Tokens - -```typescript -// El refresh token se hashea antes de guardar en BD -const hashedToken = crypto.createHash('sha256') - .update(refreshToken) - .digest('hex'); -``` - -### Validación de Propiedad - -```typescript -// Solo el dueño puede revocar sus sesiones -await this.sessionRepository.findOne({ - where: { id: sessionId, user_id: userId }, // Validación de ownership -}); -``` - ---- - -## Flujo de Sesiones - -``` -LOGIN - │ - ├─► Crear sesión con tokens - │ ├─► Limpiar expiradas - │ ├─► Si >5, eliminar antigua - │ └─► Guardar nueva sesión - │ -REFRESH - │ - ├─► Validar refresh token - ├─► Buscar sesión por hash - └─► Actualizar expiración - │ -LOGOUT - │ - └─► Marcar sesión como inactiva - └─► Establecer revoked_at -``` - ---- - -## Seguridad - -- Refresh tokens nunca se almacenan en texto plano -- Validación de ownership en revocación -- Soft delete con `is_active = false` y `revoked_at` -- Limpieza periódica de datos obsoletos -- No se serializa `refresh_token` en respuestas - ---- - -## Adaptaciones Necesarias - -1. **Límite de sesiones**: Ajustar `MAX_SESSIONS_PER_USER` según necesidades -2. **Tiempo de expiración**: Configurar según política de seguridad -3. **Geo-location**: Implementar si se requiere país/ciudad -4. **Multi-tenant**: Omitir `tenant_id` si no aplica -5. **Cron schedule**: Ajustar frecuencia de limpieza - ---- - -## Referencias - -- Código completo: `projects/gamilit/apps/backend/src/modules/auth/services/session-management.service.ts` -- Entity: `projects/gamilit/apps/backend/src/modules/auth/entities/user-session.entity.ts` - ---- - -**Mantenido por:** Sistema NEXUS -**Proyecto origen:** Gamilit Platform diff --git a/shared/libs/session-management/_reference/session-management.service.reference.ts b/shared/libs/session-management/_reference/session-management.service.reference.ts deleted file mode 100644 index aaaa7ce40..000000000 --- a/shared/libs/session-management/_reference/session-management.service.reference.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * SESSION MANAGEMENT SERVICE - REFERENCE IMPLEMENTATION - * - * @description Servicio para gestión de sesiones de usuario. - * Mantiene registro de sesiones activas, dispositivos y metadata. - * - * @usage Copiar y adaptar según necesidades del proyecto. - * @origin gamilit/apps/backend/src/modules/auth/services/session-management.service.ts - */ - -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, MoreThan } from 'typeorm'; -import * as crypto from 'crypto'; - -// Adaptar imports según proyecto -// import { UserSession } from '../entities'; - -@Injectable() -export class SessionManagementService { - constructor( - @InjectRepository(UserSession, 'auth') - private readonly sessionRepository: Repository, - ) {} - - /** - * Crear nueva sesión - * - * @param userId - ID del usuario - * @param metadata - Información del dispositivo/cliente - * @returns Sesión creada - */ - async createSession( - userId: string, - metadata: SessionMetadata, - ): Promise { - const session = this.sessionRepository.create({ - user_id: userId, - refresh_token: this.generateTokenHash(), - ip_address: metadata.ip, - user_agent: metadata.userAgent, - device_type: this.detectDeviceType(metadata.userAgent), - expires_at: this.calculateExpiry(7, 'days'), - is_revoked: false, - }); - - return this.sessionRepository.save(session); - } - - /** - * Obtener sesiones activas de un usuario - */ - async getActiveSessions(userId: string): Promise { - return this.sessionRepository.find({ - where: { - user_id: userId, - is_revoked: false, - expires_at: MoreThan(new Date()), - }, - order: { last_activity_at: 'DESC' }, - }); - } - - /** - * Revocar una sesión específica - */ - async revokeSession(sessionId: string, userId: string): Promise { - const result = await this.sessionRepository.update( - { id: sessionId, user_id: userId }, - { is_revoked: true }, - ); - - if (result.affected === 0) { - throw new NotFoundException('Sesión no encontrada'); - } - } - - /** - * Revocar todas las sesiones de un usuario (excepto la actual) - */ - async revokeAllOtherSessions(userId: string, currentSessionId: string): Promise { - const result = await this.sessionRepository - .createQueryBuilder() - .update() - .set({ is_revoked: true }) - .where('user_id = :userId', { userId }) - .andWhere('id != :currentSessionId', { currentSessionId }) - .andWhere('is_revoked = false') - .execute(); - - return result.affected || 0; - } - - /** - * Actualizar última actividad de sesión - */ - async updateLastActivity(sessionId: string): Promise { - await this.sessionRepository.update(sessionId, { - last_activity_at: new Date(), - }); - } - - /** - * Validar sesión por refresh token - */ - async validateSession(refreshTokenHash: string): Promise { - return this.sessionRepository.findOne({ - where: { - refresh_token: refreshTokenHash, - is_revoked: false, - expires_at: MoreThan(new Date()), - }, - relations: ['user'], - }); - } - - /** - * Limpiar sesiones expiradas (para CRON job) - */ - async cleanupExpiredSessions(): Promise { - const result = await this.sessionRepository - .createQueryBuilder() - .delete() - .where('expires_at < :now', { now: new Date() }) - .orWhere('is_revoked = true') - .execute(); - - return result.affected || 0; - } - - // ============ HELPERS PRIVADOS ============ - - private generateTokenHash(): string { - return crypto.randomBytes(32).toString('hex'); - } - - private detectDeviceType(userAgent: string): string { - if (/mobile/i.test(userAgent)) return 'mobile'; - if (/tablet/i.test(userAgent)) return 'tablet'; - return 'desktop'; - } - - private calculateExpiry(value: number, unit: 'hours' | 'days'): Date { - const ms = unit === 'hours' ? value * 3600000 : value * 86400000; - return new Date(Date.now() + ms); - } -} - -// ============ TIPOS ============ - -interface SessionMetadata { - ip?: string; - userAgent?: string; -} - -interface UserSession { - id: string; - user_id: string; - refresh_token: string; - ip_address?: string; - user_agent?: string; - device_type?: string; - expires_at: Date; - is_revoked: boolean; - last_activity_at?: Date; - user?: any; -} diff --git a/shared/libs/websocket/IMPLEMENTATION.md b/shared/libs/websocket/IMPLEMENTATION.md deleted file mode 100644 index 6836c1327..000000000 --- a/shared/libs/websocket/IMPLEMENTATION.md +++ /dev/null @@ -1,810 +0,0 @@ -# Guía de Implementación: WebSocket - -**Versión:** 1.0.0 -**Tiempo estimado:** 1-2 horas -**Complejidad:** Media - ---- - -## Pre-requisitos - -- [ ] Proyecto NestJS existente -- [ ] Sistema de autenticación JWT funcionando -- [ ] (Opcional) Redis para escalabilidad horizontal - ---- - -## Paso 1: Instalar Dependencias - -```bash -npm install @nestjs/websockets @nestjs/platform-socket.io socket.io - -# Ya deberías tener @nestjs/jwt del módulo de auth -``` - ---- - -## Paso 2: Crear Estructura de Directorios - -```bash -mkdir -p src/modules/websocket/gateways -mkdir -p src/modules/websocket/services -mkdir -p src/modules/websocket/guards -mkdir -p src/modules/websocket/types -``` - ---- - -## Paso 3: Definir Tipos y Eventos - -```typescript -// src/modules/websocket/types/websocket.types.ts - -/** - * Enum de eventos WebSocket - * Usar namespace:action para claridad - */ -export enum SocketEvent { - // Conexión - AUTHENTICATED = 'authenticated', - ERROR = 'error', - - // Notificaciones - NEW_NOTIFICATION = 'notification:new', - NOTIFICATION_READ = 'notification:read', - NOTIFICATION_DELETED = 'notification:deleted', - UNREAD_COUNT_UPDATED = 'notification:unread_count', - MARK_AS_READ = 'notification:mark_read', - - // Genéricos - DATA_UPDATED = 'data:updated', - USER_ONLINE = 'user:online', - USER_OFFLINE = 'user:offline', -} - -/** - * Datos del usuario en el socket - */ -export interface SocketUserData { - userId: string; - email: string; - role: string; - tenantId?: string; -} - -/** - * Payload base con timestamp - */ -export interface BasePayload { - timestamp: string; -} - -/** - * Payload de notificación - */ -export interface NotificationPayload extends BasePayload { - notification: { - id: string; - title: string; - message: string; - type: string; - data?: Record; - }; -} -``` - ---- - -## Paso 4: Crear Guard de Autenticación JWT - -```typescript -// src/modules/websocket/guards/ws-jwt.guard.ts -import { - CanActivate, - ExecutionContext, - Injectable, - Logger, -} from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { WsException } from '@nestjs/websockets'; -import { Socket } from 'socket.io'; - -/** - * Socket autenticado con datos del usuario - */ -export interface AuthenticatedSocket extends Socket { - userData?: { - userId: string; - email: string; - role: string; - tenantId?: string; - }; -} - -@Injectable() -export class WsJwtGuard implements CanActivate { - private readonly logger = new Logger(WsJwtGuard.name); - - constructor(private readonly jwtService: JwtService) {} - - async canActivate(context: ExecutionContext): Promise { - try { - const client: AuthenticatedSocket = context.switchToWs().getClient(); - - // Extraer token de auth o query params - const token = - client.handshake.auth?.token || - client.handshake.query?.token; - - if (!token || typeof token !== 'string') { - this.logger.warn('WebSocket: conexión sin token'); - throw new WsException('Token de autenticación requerido'); - } - - // Verificar JWT - const payload = await this.jwtService.verifyAsync(token); - - // Adjuntar datos al socket - client.userData = { - userId: payload.sub, - email: payload.email, - role: payload.role, - tenantId: payload.tenant_id, - }; - - this.logger.log(`WebSocket autenticado: ${client.userData.email}`); - return true; - } catch (error) { - const msg = error instanceof Error ? error.message : 'Error desconocido'; - this.logger.warn(`WebSocket auth falló: ${msg}`); - throw new WsException('Autenticación fallida'); - } - } -} -``` - ---- - -## Paso 5: Crear Gateway Principal - -```typescript -// src/modules/websocket/gateways/notifications.gateway.ts -import { - WebSocketGateway, - WebSocketServer, - SubscribeMessage, - OnGatewayConnection, - OnGatewayDisconnect, - OnGatewayInit, - ConnectedSocket, - MessageBody, -} from '@nestjs/websockets'; -import { Logger, UseGuards } from '@nestjs/common'; -import { Server } from 'socket.io'; -import { WsJwtGuard, AuthenticatedSocket } from '../guards/ws-jwt.guard'; -import { SocketEvent } from '../types/websocket.types'; - -@WebSocketGateway({ - cors: { - origin: process.env.CORS_ORIGIN?.split(',') || [ - 'http://localhost:3000', - 'http://localhost:5173', - ], - credentials: true, - methods: ['GET', 'POST'], - }, - path: '/socket.io/', - transports: ['websocket', 'polling'], -}) -export class NotificationsGateway - implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect -{ - @WebSocketServer() - server!: Server; - - private readonly logger = new Logger(NotificationsGateway.name); - - // Map: userId -> Set de socketIds - private userSockets = new Map>(); - - /** - * Lifecycle: Gateway inicializado - */ - afterInit(server: Server) { - this.logger.log('WebSocket Gateway inicializado'); - } - - /** - * Lifecycle: Cliente conectado - */ - @UseGuards(WsJwtGuard) - async handleConnection(client: AuthenticatedSocket) { - const userId = client.userData?.userId; - const userEmail = client.userData?.email; - - if (!userId) { - this.logger.warn('Conexión rechazada: sin datos de usuario'); - client.disconnect(); - return; - } - - this.logger.log(`Cliente conectado: ${userEmail} (${client.id})`); - - // Registrar socket del usuario - if (!this.userSockets.has(userId)) { - this.userSockets.set(userId, new Set()); - } - this.userSockets.get(userId)!.add(client.id); - - // Unirse a room personal - await client.join(`user:${userId}`); - this.logger.debug(`Socket ${client.id} unido a room: user:${userId}`); - - // Emitir evento de autenticación exitosa - client.emit(SocketEvent.AUTHENTICATED, { - success: true, - userId, - email: userEmail, - socketId: client.id, - }); - } - - /** - * Lifecycle: Cliente desconectado - */ - async handleDisconnect(client: AuthenticatedSocket) { - const userId = client.userData?.userId; - const userEmail = client.userData?.email; - - if (userId) { - const sockets = this.userSockets.get(userId); - if (sockets) { - sockets.delete(client.id); - if (sockets.size === 0) { - this.userSockets.delete(userId); - } - } - } - - this.logger.log(`Cliente desconectado: ${userEmail} (${client.id})`); - } - - /** - * Handler: Marcar notificación como leída - */ - @UseGuards(WsJwtGuard) - @SubscribeMessage(SocketEvent.MARK_AS_READ) - async handleMarkAsRead( - @ConnectedSocket() client: AuthenticatedSocket, - @MessageBody() data: { notificationId: string }, - ) { - try { - const userId = client.userData!.userId; - const { notificationId } = data; - - this.logger.debug( - `Usuario ${userId} marca notificación ${notificationId} como leída`, - ); - - // Aquí podrías llamar a NotificationService.markAsRead(notificationId, userId) - // await this.notificationService.markAsRead(notificationId, userId); - - // Confirmar al cliente - client.emit(SocketEvent.NOTIFICATION_READ, { - notificationId, - success: true, - }); - - return { success: true }; - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : 'Error desconocido'; - this.logger.error('Error marcando como leída:', error); - client.emit(SocketEvent.ERROR, { - message: 'Error al marcar notificación como leída', - }); - return { success: false, error: errorMessage }; - } - } - - // ===================================================== - // Métodos públicos para emitir eventos - // ===================================================== - - /** - * Emitir a un usuario específico (todos sus dispositivos) - */ - emitToUser(userId: string, event: SocketEvent, data: any) { - const room = `user:${userId}`; - this.server.to(room).emit(event, { - ...data, - timestamp: new Date().toISOString(), - }); - this.logger.debug(`Emitido ${event} a usuario ${userId}`); - } - - /** - * Emitir a múltiples usuarios - */ - emitToUsers(userIds: string[], event: SocketEvent, data: any) { - userIds.forEach((userId) => { - this.emitToUser(userId, event, data); - }); - this.logger.debug(`Emitido ${event} a ${userIds.length} usuarios`); - } - - /** - * Broadcast a todos los conectados - */ - broadcast(event: SocketEvent, data: any) { - this.server.emit(event, { - ...data, - timestamp: new Date().toISOString(), - }); - this.logger.debug(`Broadcast ${event} a todos los usuarios`); - } - - /** - * Emitir a una room específica - */ - emitToRoom(room: string, event: SocketEvent, data: any) { - this.server.to(room).emit(event, { - ...data, - timestamp: new Date().toISOString(), - }); - this.logger.debug(`Emitido ${event} a room ${room}`); - } - - // ===================================================== - // Métodos de utilidad - // ===================================================== - - /** - * Obtener cantidad de usuarios conectados - */ - getConnectedUsersCount(): number { - return this.userSockets.size; - } - - /** - * Verificar si usuario está conectado - */ - isUserConnected(userId: string): boolean { - return ( - this.userSockets.has(userId) && this.userSockets.get(userId)!.size > 0 - ); - } - - /** - * Obtener cantidad de sockets de un usuario - */ - getUserSocketCount(userId: string): number { - return this.userSockets.get(userId)?.size || 0; - } -} -``` - ---- - -## Paso 6: Crear Servicio de WebSocket - -```typescript -// src/modules/websocket/services/websocket.service.ts -import { Injectable, Logger } from '@nestjs/common'; -import { NotificationsGateway } from '../gateways/notifications.gateway'; -import { SocketEvent } from '../types/websocket.types'; - -/** - * WebSocketService - * - * API limpia para que otros módulos emitan eventos en tiempo real - */ -@Injectable() -export class WebSocketService { - private readonly logger = new Logger(WebSocketService.name); - - constructor(private readonly gateway: NotificationsGateway) {} - - /** - * Emitir notificación a un usuario - */ - emitNotificationToUser(userId: string, notification: any) { - this.gateway.emitToUser(userId, SocketEvent.NEW_NOTIFICATION, { - notification, - }); - this.logger.debug(`Notificación enviada a usuario ${userId}`); - } - - /** - * Emitir notificación a múltiples usuarios - */ - emitNotificationToUsers(userIds: string[], notification: any) { - this.gateway.emitToUsers(userIds, SocketEvent.NEW_NOTIFICATION, { - notification, - }); - this.logger.debug(`Notificación enviada a ${userIds.length} usuarios`); - } - - /** - * Actualizar contador de no leídas - */ - emitUnreadCountUpdate(userId: string, unreadCount: number) { - this.gateway.emitToUser(userId, SocketEvent.UNREAD_COUNT_UPDATED, { - unreadCount, - }); - this.logger.debug(`Contador (${unreadCount}) enviado a usuario ${userId}`); - } - - /** - * Notificación eliminada - */ - emitNotificationDeleted(userId: string, notificationId: string) { - this.gateway.emitToUser(userId, SocketEvent.NOTIFICATION_DELETED, { - notificationId, - }); - this.logger.debug( - `Eliminación ${notificationId} enviada a usuario ${userId}`, - ); - } - - /** - * Emitir datos actualizados genérico - */ - emitDataUpdated(userId: string, dataType: string, data: any) { - this.gateway.emitToUser(userId, SocketEvent.DATA_UPDATED, { - type: dataType, - data, - }); - } - - /** - * Broadcast a todos los usuarios - */ - broadcast(event: SocketEvent, data: any) { - this.gateway.broadcast(event, data); - } - - /** - * Emitir a room específica - */ - emitToRoom(room: string, event: SocketEvent, data: any) { - this.gateway.emitToRoom(room, event, data); - } - - /** - * Verificar si usuario está conectado - */ - isUserConnected(userId: string): boolean { - return this.gateway.isUserConnected(userId); - } - - /** - * Obtener usuarios conectados - */ - getConnectedUsersCount(): number { - return this.gateway.getConnectedUsersCount(); - } - - /** - * Obtener cantidad de sockets de un usuario - */ - getUserSocketCount(userId: string): number { - return this.gateway.getUserSocketCount(userId); - } -} -``` - ---- - -## Paso 7: Crear Módulo - -```typescript -// src/modules/websocket/websocket.module.ts -import { Module } from '@nestjs/common'; -import { JwtModule } from '@nestjs/jwt'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { NotificationsGateway } from './gateways/notifications.gateway'; -import { WebSocketService } from './services/websocket.service'; -import { WsJwtGuard } from './guards/ws-jwt.guard'; - -@Module({ - imports: [ - JwtModule.registerAsync({ - imports: [ConfigModule], - inject: [ConfigService], - useFactory: (config: ConfigService) => ({ - secret: config.get('JWT_SECRET'), - signOptions: { - expiresIn: config.get('JWT_EXPIRES_IN', '7d'), - }, - }), - }), - ], - providers: [NotificationsGateway, WebSocketService, WsJwtGuard], - exports: [WebSocketService], -}) -export class WebSocketModule {} - -// Barrel export -// src/modules/websocket/index.ts -export * from './websocket.module'; -export * from './services/websocket.service'; -export * from './types/websocket.types'; -export * from './guards/ws-jwt.guard'; -``` - ---- - -## Paso 8: Registrar en AppModule - -```typescript -// src/app.module.ts -import { Module } from '@nestjs/common'; -import { WebSocketModule } from './modules/websocket'; - -@Module({ - imports: [ - // ... otros módulos - WebSocketModule, - ], -}) -export class AppModule {} -``` - ---- - -## Paso 9: Uso en Otros Servicios - -```typescript -// src/modules/notifications/services/notification.service.ts -import { Injectable } from '@nestjs/common'; -import { WebSocketService } from '@/modules/websocket'; - -@Injectable() -export class NotificationService { - constructor( - private readonly notificationRepo: Repository, - private readonly wsService: WebSocketService, - ) {} - - async create(userId: string, dto: CreateNotificationDto) { - // 1. Guardar en BD - const notification = this.notificationRepo.create({ - ...dto, - user_id: userId, - }); - await this.notificationRepo.save(notification); - - // 2. Emitir por WebSocket si está conectado - if (this.wsService.isUserConnected(userId)) { - this.wsService.emitNotificationToUser(userId, notification); - } - - // 3. Actualizar contador - const unread = await this.countUnread(userId); - this.wsService.emitUnreadCountUpdate(userId, unread); - - return notification; - } - - async markAsRead(notificationId: string, userId: string) { - await this.notificationRepo.update(notificationId, { status: 'read' }); - - // Actualizar contador - const unread = await this.countUnread(userId); - this.wsService.emitUnreadCountUpdate(userId, unread); - } -} -``` - ---- - -## Paso 10: Cliente Frontend (React) - -### Instalación - -```bash -npm install socket.io-client -``` - -### Hook de WebSocket - -```typescript -// src/hooks/useSocket.ts -import { useEffect, useRef, useCallback, useState } from 'react'; -import { io, Socket } from 'socket.io-client'; -import { useAuth } from './useAuth'; - -interface UseSocketReturn { - socket: Socket | null; - isConnected: boolean; - on: (event: string, handler: (data: any) => void) => () => void; - emit: (event: string, data: any) => void; -} - -export function useSocket(): UseSocketReturn { - const { token } = useAuth(); - const socketRef = useRef(null); - const [isConnected, setIsConnected] = useState(false); - - useEffect(() => { - if (!token) return; - - const socket = io(import.meta.env.VITE_API_URL || 'http://localhost:3000', { - path: '/socket.io/', - transports: ['websocket', 'polling'], - auth: { token }, - reconnectionAttempts: 5, - reconnectionDelay: 1000, - }); - - socket.on('connect', () => { - console.log('Socket conectado'); - setIsConnected(true); - }); - - socket.on('disconnect', (reason) => { - console.log('Socket desconectado:', reason); - setIsConnected(false); - }); - - socket.on('authenticated', (data) => { - console.log('Autenticado:', data.email); - }); - - socket.on('error', (error) => { - console.error('Error de socket:', error); - }); - - socketRef.current = socket; - - return () => { - socket.disconnect(); - socketRef.current = null; - }; - }, [token]); - - const on = useCallback( - (event: string, handler: (data: any) => void) => { - socketRef.current?.on(event, handler); - return () => { - socketRef.current?.off(event, handler); - }; - }, - [], - ); - - const emit = useCallback((event: string, data: any) => { - socketRef.current?.emit(event, data); - }, []); - - return { - socket: socketRef.current, - isConnected, - on, - emit, - }; -} -``` - -### Uso en Componente - -```tsx -// src/components/NotificationBell.tsx -import { useEffect, useState } from 'react'; -import { useSocket } from '@/hooks/useSocket'; -import { Badge, IconButton } from '@mui/material'; -import NotificationsIcon from '@mui/icons-material/Notifications'; - -export function NotificationBell() { - const { on, isConnected } = useSocket(); - const [unreadCount, setUnreadCount] = useState(0); - - useEffect(() => { - // Escuchar actualizaciones del contador - const unsubscribe = on('notification:unread_count', (data) => { - setUnreadCount(data.unreadCount); - }); - - return unsubscribe; - }, [on]); - - useEffect(() => { - // Escuchar nuevas notificaciones - const unsubscribe = on('notification:new', (data) => { - // Mostrar toast o actualizar lista - console.log('Nueva notificación:', data.notification); - setUnreadCount((prev) => prev + 1); - }); - - return unsubscribe; - }, [on]); - - return ( - - - - - - ); -} -``` - ---- - -## Variables de Entorno - -```env -# Backend -JWT_SECRET=your-secret-key -JWT_EXPIRES_IN=7d -CORS_ORIGIN=http://localhost:3000,http://localhost:5173 - -# Frontend -VITE_API_URL=http://localhost:3000 -``` - ---- - -## Checklist de Implementación - -- [ ] Dependencias instaladas (@nestjs/websockets, socket.io) -- [ ] Tipos y eventos definidos -- [ ] WsJwtGuard creado -- [ ] NotificationsGateway implementado -- [ ] WebSocketService creado -- [ ] Módulo registrado en AppModule -- [ ] Variables de entorno configuradas -- [ ] Build pasa sin errores -- [ ] Test: conectar desde frontend -- [ ] Test: recibir evento de notificación - ---- - -## Verificar Funcionamiento - -### Backend - -```bash -# Ver logs al iniciar -npm run start:dev -# Debería mostrar: WebSocket Gateway inicializado -``` - -### Frontend - -```javascript -// En consola del navegador -const socket = io('http://localhost:3000', { - path: '/socket.io/', - auth: { token: 'your-jwt-token' }, -}); - -socket.on('authenticated', (data) => console.log('Auth:', data)); -socket.on('notification:new', (data) => console.log('Notif:', data)); -``` - ---- - -## Troubleshooting - -### "Authentication token required" -- Verificar que el token se envía en `auth: { token }` -- Verificar que el token JWT es válido - -### "CORS error" -- Verificar que `CORS_ORIGIN` incluya el origen del frontend -- Verificar que `credentials: true` está configurado - -### Conexión se desconecta inmediatamente -- Verificar que el guard no está rechazando la conexión -- Revisar logs del backend para ver el motivo - -### Eventos no llegan al cliente -- Verificar que el cliente está en el room correcto (`user:{userId}`) -- Verificar que el evento se emite con el nombre correcto - ---- - -**Versión:** 1.0.0 -**Sistema:** SIMCO Catálogo diff --git a/shared/libs/websocket/README.md b/shared/libs/websocket/README.md deleted file mode 100644 index 76300eefd..000000000 --- a/shared/libs/websocket/README.md +++ /dev/null @@ -1,449 +0,0 @@ -# Comunicación WebSocket - -**Versión:** 1.0.0 -**Origen:** projects/gamilit, projects/trading-platform -**Estado:** Producción -**Última actualización:** 2025-12-08 - ---- - -## Descripción - -Sistema de comunicación en tiempo real via Socket.IO: -- Conexiones WebSocket autenticadas con JWT -- Rooms por usuario para mensajes privados -- Broadcasting para eventos globales -- Tracking de usuarios conectados -- Multi-dispositivo (un usuario, múltiples sockets) -- Integración con sistema de notificaciones - ---- - -## Características - -| Característica | Descripción | -|----------------|-------------| -| Autenticación | JWT en handshake | -| Rooms | Por usuario (`user:{userId}`) | -| Multi-socket | Un usuario puede tener múltiples conexiones | -| Broadcast | Eventos a todos los conectados | -| Typed events | Enum de eventos tipados | -| CORS | Configuración flexible | -| Transports | WebSocket + polling fallback | - ---- - -## Stack Tecnológico - -```yaml -backend: - framework: NestJS - library: Socket.IO - auth: JWT - -frontend: - library: socket.io-client - -packages: - - "@nestjs/websockets" - - "@nestjs/platform-socket.io" - - "socket.io" - - "socket.io-client" -``` - ---- - -## Dependencias NPM - -```json -{ - "@nestjs/websockets": "^10.x", - "@nestjs/platform-socket.io": "^10.x", - "socket.io": "^4.x", - "@nestjs/jwt": "^10.x" -} -``` - -Frontend: -```json -{ - "socket.io-client": "^4.x" -} -``` - ---- - -## Tablas Requeridas - -No requiere tablas adicionales. Usa autenticación existente (JWT). - ---- - -## Estructura del Módulo - -``` -websocket/ -├── websocket.module.ts -├── gateways/ -│ └── notifications.gateway.ts # Gateway principal -├── services/ -│ └── websocket.service.ts # API para otros módulos -├── guards/ -│ └── ws-jwt.guard.ts # Autenticación JWT -└── types/ - └── websocket.types.ts # Eventos y tipos -``` - ---- - -## Arquitectura - -``` -┌─────────────────┐ ┌─────────────────┐ -│ Frontend │ │ Frontend │ -│ (User A) │ │ (User A) │ -│ Device 1 │ │ Device 2 │ -└────────┬────────┘ └────────┬────────┘ - │ │ - │ WebSocket │ WebSocket - │ │ - ▼ ▼ -┌─────────────────────────────────────────────┐ -│ Socket.IO Server │ -│ ┌───────────────────────────────────────┐ │ -│ │ Room: user:user-a-uuid │ │ -│ │ - socket-id-1 │ │ -│ │ - socket-id-2 │ │ -│ └───────────────────────────────────────┘ │ -│ │ -│ userSockets Map: │ -│ user-a-uuid → Set(socket-id-1, socket-id-2)│ -└─────────────────────────────────────────────┘ -``` - ---- - -## Eventos Disponibles - -### Eventos del Servidor (Server → Client) - -| Evento | Descripción | Payload | -|--------|-------------|---------| -| `authenticated` | Conexión autenticada | `{ success, userId, email }` | -| `error` | Error en operación | `{ message }` | -| `notification:new` | Nueva notificación | `{ notification, timestamp }` | -| `notification:read` | Notificación leída | `{ notificationId, success }` | -| `notification:deleted` | Notificación eliminada | `{ notificationId }` | -| `notification:unread_count` | Contador actualizado | `{ unreadCount }` | -| `achievement:unlocked` | Logro desbloqueado | `{ achievementId, title, ... }` | -| `rank:updated` | Cambio de rango | `{ newRank, oldRank }` | -| `xp:gained` | XP ganado | `{ amount, source, totalXp }` | -| `leaderboard:updated` | Leaderboard actualizado | `{ leaderboard[] }` | - -### Eventos del Cliente (Client → Server) - -| Evento | Descripción | Payload | -|--------|-------------|---------| -| `notification:mark_read` | Marcar como leída | `{ notificationId }` | - ---- - -## Uso Rápido - -### 1. Emitir desde otro servicio - -```typescript -import { WebSocketService } from '@/modules/websocket'; - -@Injectable() -export class NotificationService { - constructor(private readonly wsService: WebSocketService) {} - - async sendNotification(userId: string, notification: any) { - // Guardar en DB... - - // Emitir en tiempo real - this.wsService.emitNotificationToUser(userId, notification); - } -} -``` - -### 2. Frontend - Conectar - -```typescript -import { io, Socket } from 'socket.io-client'; - -const socket: Socket = io('http://localhost:3000', { - path: '/socket.io/', - transports: ['websocket', 'polling'], - auth: { - token: localStorage.getItem('accessToken'), - }, -}); - -// Conexión establecida -socket.on('authenticated', (data) => { - console.log('Conectado:', data.email); -}); - -// Escuchar notificaciones -socket.on('notification:new', (data) => { - showToast(data.notification.title); - updateNotificationBadge(); -}); - -// Error de autenticación -socket.on('error', (data) => { - console.error('Error:', data.message); -}); - -// Desconexión -socket.on('disconnect', (reason) => { - console.log('Desconectado:', reason); -}); -``` - -### 3. Frontend - Enviar eventos - -```typescript -// Marcar notificación como leída -socket.emit('notification:mark_read', { notificationId: 'uuid' }); -``` - -### 4. Verificar si usuario está conectado - -```typescript -// En cualquier servicio -const isOnline = this.wsService.isUserConnected(userId); - -if (isOnline) { - // Enviar por WebSocket (instantáneo) - this.wsService.emitNotificationToUser(userId, notification); -} else { - // Enviar por email o push (asíncrono) - await this.emailService.send(userId, notification); -} -``` - ---- - -## Flujo de Autenticación - -``` -1. Cliente conecta con token JWT - │ - ▼ -2. WsJwtGuard verifica token - │ - ├─► Token inválido → disconnect() - │ - └─► Token válido - │ - ▼ -3. Extraer userId, email, role del payload - │ - ▼ -4. Adjuntar userData al socket - │ - ▼ -5. Join room: user:{userId} - │ - ▼ -6. Registrar en userSockets Map - │ - ▼ -7. Emitir 'authenticated' al cliente -``` - ---- - -## Patrones de Uso - -### Notificación a un usuario - -```typescript -// El usuario recibe en todos sus dispositivos conectados -this.wsService.emitNotificationToUser(userId, { - id: notification.id, - title: notification.title, - message: notification.message, -}); -``` - -### Notificación a múltiples usuarios - -```typescript -// Ejemplo: notificar a todos los miembros de un grupo -const memberIds = ['uuid1', 'uuid2', 'uuid3']; -this.wsService.emitNotificationToUsers(memberIds, { - type: 'group_message', - groupId: group.id, - message: 'Nuevo mensaje en el grupo', -}); -``` - -### Broadcast global - -```typescript -// Ejemplo: actualización del leaderboard -this.wsService.broadcastLeaderboardUpdate(newLeaderboard); -``` - -### Eventos de gamificación - -```typescript -// Logro desbloqueado -this.wsService.emitAchievementUnlocked(userId, { - achievementId: 'ach-001', - title: 'Primer Login', - description: 'Has iniciado sesión por primera vez', - icon: '/icons/first-login.png', -}); - -// XP ganado -this.wsService.emitXpGained(userId, { - amount: 100, - source: 'daily_mission', - totalXp: 1500, -}); - -// Subida de rango -this.wsService.emitRankUpdated(userId, { - oldRank: 'Novato', - newRank: 'Aprendiz', - xpRequired: 2000, -}); -``` - ---- - -## Variables de Entorno - -```env -# WebSocket -WS_PORT=3000 # Puerto (mismo que HTTP) -WS_PATH=/socket.io/ # Path del endpoint - -# CORS -CORS_ORIGIN=http://localhost:3000,http://localhost:5173 - -# JWT (compartido con auth) -JWT_SECRET=your-secret-key -``` - ---- - -## Frontend React Hook - -```typescript -// hooks/useSocket.ts -import { useEffect, useRef, useCallback } from 'react'; -import { io, Socket } from 'socket.io-client'; -import { useAuth } from './useAuth'; - -export function useSocket() { - const { token } = useAuth(); - const socketRef = useRef(null); - - useEffect(() => { - if (!token) return; - - socketRef.current = io(import.meta.env.VITE_API_URL, { - path: '/socket.io/', - transports: ['websocket', 'polling'], - auth: { token }, - }); - - socketRef.current.on('connect', () => { - console.log('Socket connected'); - }); - - socketRef.current.on('disconnect', (reason) => { - console.log('Socket disconnected:', reason); - }); - - return () => { - socketRef.current?.disconnect(); - }; - }, [token]); - - const on = useCallback((event: string, handler: (data: any) => void) => { - socketRef.current?.on(event, handler); - return () => socketRef.current?.off(event, handler); - }, []); - - const emit = useCallback((event: string, data: any) => { - socketRef.current?.emit(event, data); - }, []); - - return { socket: socketRef.current, on, emit }; -} - -// Uso en componente -function NotificationBell() { - const { on } = useSocket(); - const [unread, setUnread] = useState(0); - - useEffect(() => { - return on('notification:unread_count', (data) => { - setUnread(data.unreadCount); - }); - }, [on]); - - return ; -} -``` - ---- - -## Consideraciones de Escalabilidad - -### Múltiples instancias (Horizontal Scaling) - -Para escalar horizontalmente con múltiples instancias del servidor: - -```typescript -// Usar Redis adapter para Socket.IO -import { createAdapter } from '@socket.io/redis-adapter'; -import { createClient } from 'redis'; - -const pubClient = createClient({ url: process.env.REDIS_URL }); -const subClient = pubClient.duplicate(); - -io.adapter(createAdapter(pubClient, subClient)); -``` - -### Sticky Sessions - -Con load balancer, configurar sticky sessions para que un cliente siempre llegue a la misma instancia: - -```nginx -upstream websocket { - ip_hash; # Sticky sessions por IP - server backend1:3000; - server backend2:3000; -} -``` - ---- - -## Adaptaciones Necesarias - -1. **Eventos**: Definir eventos específicos de tu aplicación -2. **Rooms**: Agregar rooms adicionales (grupos, chats, etc.) -3. **Auth**: Ajustar extracción de datos del JWT -4. **Scaling**: Configurar Redis adapter si múltiples instancias -5. **CORS**: Ajustar orígenes permitidos - ---- - -## Referencias - -- [Socket.IO Documentation](https://socket.io/docs/v4/) -- [NestJS WebSockets](https://docs.nestjs.com/websockets/gateways) -- [Socket.IO Redis Adapter](https://socket.io/docs/v4/redis-adapter/) - ---- - -**Mantenido por:** Sistema NEXUS -**Proyectos origen:** Gamilit Platform, Trading Platform diff --git a/shared/libs/websocket/_reference/websocket.gateway.reference.ts b/shared/libs/websocket/_reference/websocket.gateway.reference.ts deleted file mode 100644 index dc3dd4f03..000000000 --- a/shared/libs/websocket/_reference/websocket.gateway.reference.ts +++ /dev/null @@ -1,198 +0,0 @@ -/** - * WEBSOCKET GATEWAY - REFERENCE IMPLEMENTATION - * - * @description Gateway WebSocket para comunicación en tiempo real. - * Soporta autenticación JWT, rooms y eventos tipados. - * - * @usage - * ```typescript - * // En el cliente - * const socket = io('http://localhost:3000', { - * auth: { token: 'jwt-token' } - * }); - * socket.emit('join-room', { roomId: 'room-1' }); - * socket.on('message', (data) => console.log(data)); - * ``` - * - * @origin gamilit (patrón base) - */ - -import { - WebSocketGateway, - WebSocketServer, - SubscribeMessage, - OnGatewayConnection, - OnGatewayDisconnect, - ConnectedSocket, - MessageBody, -} from '@nestjs/websockets'; -import { Server, Socket } from 'socket.io'; -import { Logger, UseGuards } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; - -// Adaptar imports según proyecto -// import { WsAuthGuard } from '../guards'; - -@WebSocketGateway({ - cors: { - origin: process.env.CORS_ORIGIN || '*', - credentials: true, - }, - namespace: '/ws', -}) -export class AppWebSocketGateway implements OnGatewayConnection, OnGatewayDisconnect { - @WebSocketServer() - server: Server; - - private readonly logger = new Logger(AppWebSocketGateway.name); - private readonly connectedClients = new Map(); - - constructor(private readonly jwtService: JwtService) {} - - /** - * Manejar nueva conexión - */ - async handleConnection(client: Socket) { - try { - // Extraer y validar token - const token = client.handshake.auth?.token || client.handshake.headers?.authorization?.replace('Bearer ', ''); - - if (!token) { - this.disconnect(client, 'No token provided'); - return; - } - - const payload = this.jwtService.verify(token); - - // Almacenar cliente conectado - this.connectedClients.set(client.id, { - socketId: client.id, - userId: payload.sub, - role: payload.role, - connectedAt: new Date(), - }); - - // Auto-join a room del usuario - client.join(`user:${payload.sub}`); - - this.logger.log(`Client connected: ${client.id} (user: ${payload.sub})`); - client.emit('connected', { message: 'Successfully connected' }); - - } catch (error) { - this.disconnect(client, 'Invalid token'); - } - } - - /** - * Manejar desconexión - */ - handleDisconnect(client: Socket) { - const clientInfo = this.connectedClients.get(client.id); - this.connectedClients.delete(client.id); - this.logger.log(`Client disconnected: ${client.id} (user: ${clientInfo?.userId || 'unknown'})`); - } - - /** - * Unirse a una sala - */ - @SubscribeMessage('join-room') - handleJoinRoom( - @ConnectedSocket() client: Socket, - @MessageBody() data: { roomId: string }, - ) { - client.join(data.roomId); - this.logger.debug(`Client ${client.id} joined room: ${data.roomId}`); - return { event: 'room-joined', data: { roomId: data.roomId } }; - } - - /** - * Salir de una sala - */ - @SubscribeMessage('leave-room') - handleLeaveRoom( - @ConnectedSocket() client: Socket, - @MessageBody() data: { roomId: string }, - ) { - client.leave(data.roomId); - this.logger.debug(`Client ${client.id} left room: ${data.roomId}`); - return { event: 'room-left', data: { roomId: data.roomId } }; - } - - /** - * Enviar mensaje a una sala - */ - @SubscribeMessage('message') - handleMessage( - @ConnectedSocket() client: Socket, - @MessageBody() data: { roomId: string; message: string }, - ) { - const clientInfo = this.connectedClients.get(client.id); - - // Broadcast a la sala (excepto al sender) - client.to(data.roomId).emit('message', { - senderId: clientInfo?.userId, - message: data.message, - timestamp: new Date().toISOString(), - }); - - return { event: 'message-sent', data: { success: true } }; - } - - // ============ MÉTODOS PÚBLICOS PARA SERVICIOS ============ - - /** - * Enviar notificación a un usuario específico - */ - sendToUser(userId: string, event: string, data: any) { - this.server.to(`user:${userId}`).emit(event, data); - } - - /** - * Enviar a una sala - */ - sendToRoom(roomId: string, event: string, data: any) { - this.server.to(roomId).emit(event, data); - } - - /** - * Broadcast a todos los clientes conectados - */ - broadcast(event: string, data: any) { - this.server.emit(event, data); - } - - /** - * Obtener clientes conectados en una sala - */ - async getClientsInRoom(roomId: string): Promise { - const sockets = await this.server.in(roomId).fetchSockets(); - return sockets.map(s => s.id); - } - - /** - * Verificar si un usuario está conectado - */ - isUserConnected(userId: string): boolean { - for (const client of this.connectedClients.values()) { - if (client.userId === userId) return true; - } - return false; - } - - // ============ HELPERS PRIVADOS ============ - - private disconnect(client: Socket, reason: string) { - client.emit('error', { message: reason }); - client.disconnect(true); - this.logger.warn(`Client ${client.id} disconnected: ${reason}`); - } -} - -// ============ TIPOS ============ - -interface ConnectedClient { - socketId: string; - userId: string; - role: string; - connectedAt: Date; -} diff --git a/core/modules/auth/README.md b/shared/modules/auth/README.md similarity index 83% rename from core/modules/auth/README.md rename to shared/modules/auth/README.md index a5395bede..da94eee5a 100644 --- a/core/modules/auth/README.md +++ b/shared/modules/auth/README.md @@ -1,6 +1,6 @@ # Auth - Core Module -**Modulo:** core/modules/auth/ +**Modulo:** shared/modules/auth/ **Version:** 0.1.0 **Fecha:** 2026-01-03 **Owner:** Backend-Agent @@ -10,7 +10,7 @@ ## Descripcion -Modulo de autenticacion compartido que provee guards, decorators y utilidades JWT para proyectos NestJS. Complementa el catalogo `core/catalog/auth/` con codigo listo para importar. +Modulo de autenticacion compartido que provee guards, decorators y utilidades JWT para proyectos NestJS. Complementa el catalogo `shared/catalog/auth/` con codigo listo para importar. --- @@ -29,7 +29,7 @@ npm install -D @types/passport-jwt @types/bcrypt { "compilerOptions": { "paths": { - "@core/modules/*": ["../../core/modules/*"] + "@shared/modules/*": ["../../shared/modules/*"] } } } @@ -70,7 +70,7 @@ npm install -D @types/passport-jwt @types/bcrypt ### Ejemplo 1: Proteger Controller ```typescript -import { JwtAuthGuard, CurrentUser, Roles } from '@core/modules/auth'; +import { JwtAuthGuard, CurrentUser, Roles } from '@shared/modules/auth'; @Controller('users') @UseGuards(JwtAuthGuard) @@ -92,7 +92,7 @@ export class UsersController { ### Ejemplo 2: Hash de Password ```typescript -import { PasswordService } from '@core/modules/auth'; +import { PasswordService } from '@shared/modules/auth'; const passwordService = new PasswordService(); @@ -111,7 +111,7 @@ const isValid = await passwordService.verify('myPassword123', hash); | Modulo | Uso | |--------|-----| -| `@core/modules/utils` | Validaciones | +| `@shared/modules/utils` | Validaciones | ### Externas (npm) @@ -126,9 +126,9 @@ const isValid = await passwordService.verify('myPassword123', hash); ## Relacion con Catalogo -Este modulo (`core/modules/auth/`) provee **codigo importable**. +Este modulo (`shared/modules/auth/`) provee **codigo importable**. -El catalogo (`core/catalog/auth/`) provee **documentacion y referencia** para implementaciones completas. +El catalogo (`shared/catalog/auth/`) provee **documentacion y referencia** para implementaciones completas. ```yaml Usar modules/auth cuando: @@ -168,4 +168,4 @@ Usar catalog/auth cuando: --- -**Modulo:** core/modules/auth/ | **Owner:** Backend-Agent | **Estado:** desarrollo +**Modulo:** shared/modules/auth/ | **Owner:** Backend-Agent | **Estado:** desarrollo diff --git a/core/modules/billing/README.md b/shared/modules/billing/README.md similarity index 89% rename from core/modules/billing/README.md rename to shared/modules/billing/README.md index eb6ad1222..1717c517e 100644 --- a/core/modules/billing/README.md +++ b/shared/modules/billing/README.md @@ -1,6 +1,6 @@ # Billing - Core Module -**Modulo:** core/modules/billing/ +**Modulo:** shared/modules/billing/ **Version:** 0.1.0 **Fecha:** 2026-01-03 **Owner:** Backend-Agent @@ -29,7 +29,7 @@ npm install stripe { "compilerOptions": { "paths": { - "@core/modules/*": ["../../core/modules/*"] + "@shared/modules/*": ["../../shared/modules/*"] } } } @@ -97,7 +97,7 @@ interface UsageRecord { ### Ejemplo 1: Verificar Cuota ```typescript -import { QuotaService } from '@core/modules/billing'; +import { QuotaService } from '@shared/modules/billing'; @Injectable() export class ProjectService { @@ -125,7 +125,7 @@ export class ProjectService { ### Ejemplo 2: Obtener Plan Actual ```typescript -import { PlanService, UsageService } from '@core/modules/billing'; +import { PlanService, UsageService } from '@shared/modules/billing'; @Controller('billing') export class BillingController { @@ -158,8 +158,8 @@ export class BillingController { | Modulo | Uso | |--------|-----| -| `@core/modules/payments` | Transacciones | -| `@core/modules/utils` | Formateo | +| `@shared/modules/payments` | Transacciones | +| `@shared/modules/utils` | Formateo | ### Externas (npm) @@ -191,4 +191,4 @@ export class BillingController { --- -**Modulo:** core/modules/billing/ | **Owner:** Backend-Agent +**Modulo:** shared/modules/billing/ | **Owner:** Backend-Agent diff --git a/core/modules/multitenant/README.md b/shared/modules/multitenant/README.md similarity index 91% rename from core/modules/multitenant/README.md rename to shared/modules/multitenant/README.md index 2500d7f6d..f98f9b659 100644 --- a/core/modules/multitenant/README.md +++ b/shared/modules/multitenant/README.md @@ -1,6 +1,6 @@ # Multitenant - Core Module -**Modulo:** core/modules/multitenant/ +**Modulo:** shared/modules/multitenant/ **Version:** 0.1.0 **Fecha:** 2026-01-03 **Owner:** Backend-Agent @@ -29,7 +29,7 @@ npm install typeorm pg { "compilerOptions": { "paths": { - "@core/modules/*": ["../../core/modules/*"] + "@shared/modules/*": ["../../shared/modules/*"] } } } @@ -99,7 +99,7 @@ interface TenantContext { ### Ejemplo 1: Entity con RLS ```typescript -import { TenantAware } from '@core/modules/multitenant'; +import { TenantAware } from '@shared/modules/multitenant'; @Entity('projects') @TenantAware() @@ -118,7 +118,7 @@ export class Project { ### Ejemplo 2: Acceder al Tenant Actual ```typescript -import { CurrentTenant, TenantGuard } from '@core/modules/multitenant'; +import { CurrentTenant, TenantGuard } from '@shared/modules/multitenant'; @Controller('projects') @UseGuards(TenantGuard) @@ -155,7 +155,7 @@ SET app.current_tenant_id = 'uuid-del-tenant'; | Modulo | Uso | |--------|-----| -| `@core/modules/auth` | JWT, user context | +| `@shared/modules/auth` | JWT, user context | ### Externas (npm) @@ -213,4 +213,4 @@ CREATE POLICY tenant_isolation ON {schema}.{table} --- -**Modulo:** core/modules/multitenant/ | **Owner:** Backend-Agent +**Modulo:** shared/modules/multitenant/ | **Owner:** Backend-Agent diff --git a/core/modules/notifications/README.md b/shared/modules/notifications/README.md similarity index 91% rename from core/modules/notifications/README.md rename to shared/modules/notifications/README.md index fbe80def0..0ad5a15a2 100644 --- a/core/modules/notifications/README.md +++ b/shared/modules/notifications/README.md @@ -1,6 +1,6 @@ # Notifications - Core Module -**Modulo:** core/modules/notifications/ +**Modulo:** shared/modules/notifications/ **Version:** 0.1.0 **Fecha:** 2026-01-03 **Owner:** Backend-Agent @@ -31,7 +31,7 @@ npm install firebase-admin { "compilerOptions": { "paths": { - "@core/modules/*": ["../../core/modules/*"] + "@shared/modules/*": ["../../shared/modules/*"] } } } @@ -82,7 +82,7 @@ interface NotificationPreferences { ### Ejemplo 1: Enviar Notificacion ```typescript -import { NotificationService } from '@core/modules/notifications'; +import { NotificationService } from '@shared/modules/notifications'; @Injectable() export class OrderService { @@ -123,7 +123,7 @@ await this.notifications.sendMulti({ | Modulo | Uso | |--------|-----| -| `@core/modules/utils` | Formateo, validaciones | +| `@shared/modules/utils` | Formateo, validaciones | ### Externas (npm) @@ -168,4 +168,4 @@ await this.notifications.sendMulti({ --- -**Modulo:** core/modules/notifications/ | **Owner:** Backend-Agent +**Modulo:** shared/modules/notifications/ | **Owner:** Backend-Agent diff --git a/core/modules/package.json b/shared/modules/package.json similarity index 95% rename from core/modules/package.json rename to shared/modules/package.json index 35a02e32e..92aaeb6fb 100644 --- a/core/modules/package.json +++ b/shared/modules/package.json @@ -1,5 +1,5 @@ { - "name": "@core/modules", + "name": "@shared/modules", "version": "1.0.0", "description": "Core modules compartidos para todos los proyectos del workspace", "main": "index.ts", diff --git a/core/modules/payments/README.md b/shared/modules/payments/README.md similarity index 91% rename from core/modules/payments/README.md rename to shared/modules/payments/README.md index 9dfc0e8a4..20f4aac61 100644 --- a/core/modules/payments/README.md +++ b/shared/modules/payments/README.md @@ -1,6 +1,6 @@ # Payments - Core Module -**Modulo:** core/modules/payments/ +**Modulo:** shared/modules/payments/ **Version:** 0.1.0 **Fecha:** 2026-01-03 **Owner:** Backend-Agent @@ -31,7 +31,7 @@ npm install mercadopago { "compilerOptions": { "paths": { - "@core/modules/*": ["../../core/modules/*"] + "@shared/modules/*": ["../../shared/modules/*"] } } } @@ -90,7 +90,7 @@ interface CheckoutSession { ### Ejemplo 1: Crear Checkout Session ```typescript -import { PaymentService } from '@core/modules/payments'; +import { PaymentService } from '@shared/modules/payments'; @Injectable() export class CheckoutService { @@ -116,7 +116,7 @@ export class CheckoutService { ### Ejemplo 2: Procesar Webhook ```typescript -import { WebhookService } from '@core/modules/payments'; +import { WebhookService } from '@shared/modules/payments'; @Controller('webhooks') export class WebhookController { @@ -151,7 +151,7 @@ export class WebhookController { | Modulo | Uso | |--------|-----| -| `@core/modules/utils` | Formateo moneda | +| `@shared/modules/utils` | Formateo moneda | ### Externas (npm) @@ -195,4 +195,4 @@ export class WebhookController { --- -**Modulo:** core/modules/payments/ | **Owner:** Backend-Agent +**Modulo:** shared/modules/payments/ | **Owner:** Backend-Agent diff --git a/core/modules/utils/README.md b/shared/modules/utils/README.md similarity index 96% rename from core/modules/utils/README.md rename to shared/modules/utils/README.md index 5a0d4217c..f6ca17e93 100644 --- a/core/modules/utils/README.md +++ b/shared/modules/utils/README.md @@ -1,6 +1,6 @@ # Utils - Core Module -**Modulo:** core/modules/utils/ +**Modulo:** shared/modules/utils/ **Version:** 1.0.0 **Fecha:** 2026-01-03 **Owner:** Tech-Leader @@ -28,7 +28,7 @@ Agregar en `tsconfig.json` del proyecto consumidor: { "compilerOptions": { "paths": { - "@core/modules/*": ["../../core/modules/*"] + "@shared/modules/*": ["../../shared/modules/*"] } } } @@ -141,7 +141,7 @@ Agregar en `tsconfig.json` del proyecto consumidor: ### Ejemplo 1: Formateo de Fechas ```typescript -import { formatDate, addDays, isPast, getRelativeTime } from '@core/modules/utils'; +import { formatDate, addDays, isPast, getRelativeTime } from '@shared/modules/utils'; // Formatear fecha const fecha = new Date(); @@ -160,7 +160,7 @@ console.log(getRelativeTime(ayer)); // "1 day ago" ### Ejemplo 2: Manipulacion de Strings ```typescript -import { slugify, capitalize, maskEmail, formatCurrency } from '@core/modules/utils'; +import { slugify, capitalize, maskEmail, formatCurrency } from '@shared/modules/utils'; // Generar slug const slug = slugify('Hello World 2026!'); // "hello-world-2026" @@ -179,7 +179,7 @@ const precioMXN = formatCurrency(1234.50, 'MXN', 'es-MX'); // "$1,234.50" ### Ejemplo 3: Validaciones ```typescript -import { isEmail, isStrongPassword, hasRequiredFields, createValidator } from '@core/modules/utils'; +import { isEmail, isStrongPassword, hasRequiredFields, createValidator } from '@shared/modules/utils'; // Validaciones simples console.log(isEmail('test@example.com')); // true @@ -225,7 +225,7 @@ Ninguna. Solo usa APIs nativas de JavaScript. ## Estructura de Archivos ``` -core/modules/utils/ +shared/modules/utils/ +-- index.ts # Punto de entrada, re-exporta todo +-- date.util.ts # 25 funciones de fecha +-- string.util.ts # 30 funciones de string @@ -282,4 +282,4 @@ core/modules/utils/ --- -**Modulo:** core/modules/utils/ | **Owner:** Tech-Leader | **90 funciones** +**Modulo:** shared/modules/utils/ | **Owner:** Tech-Leader | **90 funciones** diff --git a/core/modules/utils/date.util.ts b/shared/modules/utils/date.util.ts similarity index 99% rename from core/modules/utils/date.util.ts rename to shared/modules/utils/date.util.ts index ed47126a8..160ea1646 100644 --- a/core/modules/utils/date.util.ts +++ b/shared/modules/utils/date.util.ts @@ -4,7 +4,7 @@ * Framework-agnostic date manipulation and formatting functions. * Can be used in any project (NestJS, Express, Frontend, etc.) * - * @module @core/utils/date + * @module @shared/utils/date * @version 1.0.0 */ diff --git a/core/modules/utils/index.ts b/shared/modules/utils/index.ts similarity index 93% rename from core/modules/utils/index.ts rename to shared/modules/utils/index.ts index 0ee9282dc..310c346e0 100644 --- a/core/modules/utils/index.ts +++ b/shared/modules/utils/index.ts @@ -4,12 +4,12 @@ * Framework-agnostic utility functions that can be used across * all projects in the workspace (Gamilit, Trading Platform, ERP Suite, etc.) * - * @module @core/utils + * @module @shared/utils * @version 1.0.0 * * @example * ```typescript - * import { formatDate, slugify, isEmail } from '@core/utils'; + * import { formatDate, slugify, isEmail } from '@shared/utils'; * * const date = formatDate(new Date(), 'YYYY-MM-DD'); * const slug = slugify('Hello World'); diff --git a/core/modules/utils/string.util.ts b/shared/modules/utils/string.util.ts similarity index 99% rename from core/modules/utils/string.util.ts rename to shared/modules/utils/string.util.ts index 794ba8819..122943ba9 100644 --- a/core/modules/utils/string.util.ts +++ b/shared/modules/utils/string.util.ts @@ -4,7 +4,7 @@ * Framework-agnostic string manipulation and formatting functions. * Can be used in any project (NestJS, Express, Frontend, etc.) * - * @module @core/utils/string + * @module @shared/utils/string * @version 1.0.0 */ diff --git a/core/modules/utils/validation.util.ts b/shared/modules/utils/validation.util.ts similarity index 99% rename from core/modules/utils/validation.util.ts rename to shared/modules/utils/validation.util.ts index 70bc514e2..d988f5b9b 100644 --- a/core/modules/utils/validation.util.ts +++ b/shared/modules/utils/validation.util.ts @@ -4,7 +4,7 @@ * Framework-agnostic validation helper functions. * Can be used in any project (NestJS, Express, Frontend, etc.) * - * @module @core/utils/validation + * @module @shared/utils/validation * @version 1.0.0 */ diff --git a/core/types/api.types.ts b/shared/types/api.types.ts similarity index 99% rename from core/types/api.types.ts rename to shared/types/api.types.ts index 479fc5dbe..0cd23160d 100644 --- a/core/types/api.types.ts +++ b/shared/types/api.types.ts @@ -3,7 +3,7 @@ * * Tipos compartidos para APIs REST en todos los proyectos. * - * @module @core/types/api + * @module @shared/types/api * @version 1.0.0 */ diff --git a/core/types/common.types.ts b/shared/types/common.types.ts similarity index 99% rename from core/types/common.types.ts rename to shared/types/common.types.ts index 59401db9d..35a6ca798 100644 --- a/core/types/common.types.ts +++ b/shared/types/common.types.ts @@ -3,7 +3,7 @@ * * Tipos comunes compartidos entre todos los proyectos. * - * @module @core/types/common + * @module @shared/types/common * @version 1.0.0 */ diff --git a/core/types/index.ts b/shared/types/index.ts similarity index 87% rename from core/types/index.ts rename to shared/types/index.ts index 22612550f..416ed67fe 100644 --- a/core/types/index.ts +++ b/shared/types/index.ts @@ -3,7 +3,7 @@ * * Type definitions shared across all projects in the workspace. * - * @module @core/types + * @module @shared/types * @version 1.0.0 */