diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3177286..fa2f117 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,10 +10,10 @@ * @jhosepmyr # ─── Cover page, Version History, and general structure ───────────────────── -README.md @jhosepmyr @Eric396 +README.md @jhosepmyr @Eric396 @salimramirez @VarBus @Kyrubi # ─── Brand & logos ────────────────────────────────────────────────────────── -assets/brand/ @Eric396 +assets/brand/ @jhosepmyr # ─── Team photos ──────────────────────────────────────────────────────────── assets/team/ @jhosepmyr @@ -22,25 +22,28 @@ assets/team/ @jhosepmyr assets/insights/ @jhosepmyr # ─── Chapter I: Lean UX ───────────────────────────────────────────────────── -assets/lean-ux/ @Eric396 +assets/lean-ux/ @jhosepmyr # ─── Chapter II: Interviews ───────────────────────────────────────────────── -assets/interviews/ @salimramirez @VarBus +assets/interviews/ @jhosepmyr # ─── Chapter II: User Research ────────────────────────────────────────────── -assets/user-research/ @Kyrubi @jhosepmyr +assets/user-research/ @jhosepmyr # ─── Chapter IV: EventStorming ────────────────────────────────────────────── -assets/event-storming/ @Eric396 @jhosepmyr +assets/event-storming/ @jhosepmyr # ─── Chapter IV: Domain-Driven Design ─────────────────────────────────────── -assets/ddd/ @Eric396 @jhosepmyr +assets/ddd/ @jhosepmyr # ─── Chapter IV: Software Architecture (C4) ───────────────────────────────── assets/architecture/ @jhosepmyr +# ─── Chapter VI: Solution UX Design (wireframes, wireflows, mockups, style) ─ +assets/ui/ @jhosepmyr + # ─── Annexes ──────────────────────────────────────────────────────────────── -assets/annexes/ @VarBus @salimramirez +assets/annexes/ @jhosepmyr # ─── Repository governance files ──────────────────────────────────────────── CHANGELOG.md @jhosepmyr diff --git a/.github/workflows/generate-pdf.yml b/.github/workflows/generate-pdf.yml index 7dc4239..08b4475 100644 --- a/.github/workflows/generate-pdf.yml +++ b/.github/workflows/generate-pdf.yml @@ -42,12 +42,26 @@ jobs: mv README.pdf ReqsAI-Report.pdf - name: Upload artifact + id: upload uses: actions/upload-artifact@v7 with: name: ReqsAI-Report-PDF path: ReqsAI-Report.pdf retention-days: 90 + - name: Job summary + run: | + echo "## ReqsAI Report — PDF generado" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| | |" >> $GITHUB_STEP_SUMMARY + echo "|---|---|" >> $GITHUB_STEP_SUMMARY + echo "| **Commit** | \`${{ github.sha }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Branch** | \`${{ github.ref_name }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Fecha** | $(date +'%Y-%m-%d %H:%M UTC') |" >> $GITHUB_STEP_SUMMARY + echo "| **Tamaño** | $(du -sh ReqsAI-Report.pdf | cut -f1) |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### [Descargar PDF](${{ steps.upload.outputs.artifact-url }})" >> $GITHUB_STEP_SUMMARY + - name: Attach to release (if tagged) if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v3 diff --git a/README.md b/README.md index 703d257..88526b3 100644 --- a/README.md +++ b/README.md @@ -42,22 +42,25 @@
- # Registro de Versiones del Informe -| Versión | Fecha | Autor | Descripción de modificación | -|---------|------------|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 1.0 | 14/04/2026 | Eric | Creación de la estructura base del informe, portada, logos y descripción inicial del Startup. | -| 1.1 | 17/04/2026 | Eric | Adición del Background, problemáticas, proceso de Lean UX, Target Segments y perfiles del equipo (Team Profiles). | -| 1.2 | 22/04/2026 | Marcelo, Salim Ramirez | Inclusión del análisis de competidores, diseño preliminar de preguntas para entrevistas y configuración de exclusiones del repositorio (.gitignore). | -| 1.3 | 23/04/2026 | Gutierrez Soto Jhosepmyr, Eric | Estructuración de Epics, User Stories (formato BDD y criterios de aceptación), Product Backlog priorizado y definición de atributos de calidad (Quality Attribute Scenarios). | -| 1.4 | 24/04/2026 | Marcelo, Gutierrez Soto Jhosepmyr | Redacción de estrategias y tácticas, actualización de la sección de competidores, e iteración técnica de historias de usuario (criterios INVEST). | -| 1.5 | 25/04/2026 | Marcelo | Actualización de la información general del proyecto e informe. | -| 1.6 | 26/04/2026 | Paul, Gutierrez Soto Jhosepmyr | Incorporación de la sección de User Personas (Empathy Mapping, User Journey, User Task Matrix) y modelado de escenarios As-Is / To-Be. | -| 1.7 | 26/04/2026 | Eric | Desarrollo de la sección de Domain-Driven Design (Eventstorming, Context Discovery, Context Mapping, Bounded Context Canvases). | -| 1.8 | 26/04/2026 | Marcelo, Salim Ramirez, Paul | Incorporación de análisis y hallazgos de las entrevistas para roles técnicos y funcionales. Adición de sección de Impact Mapping y Student Outcomes. | -| 2.0 | 26/04/2026 | Eric, Gutierrez Soto Jhosepmyr | Inclusión de los diagramas de Arquitectura de Software C4 (System Landscape, System Context, Container y Deployment). Actualización final del Backlog en Jira y sección de conclusiones. | -| 2.1 | 09/05/2026 | Gutierrez Soto Jhosepmyr | Refinamientos por feedback TP1: ampliación uniforme de los 5 patrones de Context Mapping (ACL hacia Jira, ACL hacia LLM/STT, Customer/Supplier Discovery↔Workspace, Conformist Workspace↔Billing, OHS+PL Discovery↔Gateway) con contratos detallados, escenarios de evolución y tradeoffs explícitos; reescritura del Container Diagram con declaración explícita Monolito Modular vs. Microservicios, mapeo Bounded Context → Módulo Maven y reglas de dependencia automatizadas con ArchUnit, en tono orientado a presentación al cliente. | +| Versión | Fecha | Autor | Descripción de modificación | +|---------|------------|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 1.0 | 14/04/2026 | Eric | Creación de la estructura base del informe, portada, logos y descripción inicial del Startup. | +| 1.1 | 17/04/2026 | Eric | Adición del Background, problemáticas, proceso de Lean UX, Target Segments y perfiles del equipo (Team Profiles). | +| 1.2 | 22/04/2026 | Marcelo, Salim Ramirez | Inclusión del análisis de competidores, diseño preliminar de preguntas para entrevistas y configuración de exclusiones del repositorio (.gitignore). | +| 1.3 | 23/04/2026 | Gutierrez Soto Jhosepmyr, Eric | Estructuración de Epics, User Stories (formato BDD y criterios de aceptación), Product Backlog priorizado y definición de atributos de calidad (Quality Attribute Scenarios). | +| 1.4 | 24/04/2026 | Marcelo, Gutierrez Soto Jhosepmyr | Redacción de estrategias y tácticas, actualización de la sección de competidores, e iteración técnica de historias de usuario (criterios INVEST). | +| 1.5 | 25/04/2026 | Marcelo | Actualización de la información general del proyecto e informe. | +| 1.6 | 26/04/2026 | Paul, Gutierrez Soto Jhosepmyr | Incorporación de la sección de User Personas (Empathy Mapping, User Journey, User Task Matrix) y modelado de escenarios As-Is / To-Be. | +| 1.7 | 26/04/2026 | Eric | Desarrollo de la sección de Domain-Driven Design (Eventstorming, Context Discovery, Context Mapping, Bounded Context Canvases). | +| 1.8 | 26/04/2026 | Marcelo, Salim Ramirez, Paul | Incorporación de análisis y hallazgos de las entrevistas para roles técnicos y funcionales. Adición de sección de Impact Mapping y Student Outcomes. | +| 2.0 | 26/04/2026 | Eric, Gutierrez Soto Jhosepmyr | Inclusión de los diagramas de Arquitectura de Software C4 (System Landscape, System Context, Container y Deployment). Actualización final del Backlog en Jira y sección de conclusiones. | +| 2.1 | 09/05/2026 | Gutierrez Soto Jhosepmyr | Refinamientos por feedback TP1: ampliación uniforme de los 5 patrones de Context Mapping (ACL hacia Jira, ACL hacia LLM/STT, Customer/Supplier Discovery↔Workspace, Conformist Workspace↔Billing, OHS+PL Discovery↔Gateway) con contratos detallados, escenarios de evolución y tradeoffs explícitos; reescritura del Container Diagram con declaración explícita Monolito Modular vs. Microservicios, mapeo Bounded Context → Módulo Maven y reglas de dependencia automatizadas con ArchUnit, en tono orientado a presentación al cliente. | +| 2.2 | 13/05/2026 | Marcelo | Elaboración de wireframes, wireflows y mock-ups iniciales para la aplicación web. | +| 2.3 | 14/05/2026 | Gutierrez Soto Jhosepmyr, Marcelo, Eric | Expansión del inventario de funcionalidades, refinamiento de User Stories y Backlog, inclusión de directrices de estilo de diseño (UI/UX) y corrección de formato. | +| 2.4 | 15/05/2026 | Gutierrez Soto Jhosepmyr, Eric, Paul | Desarrollo detallado del diseño de software a nivel táctico (Capítulo V): estructuración de capas de dominio, infraestructura e interfaz. Actualización de diagramas C4 y modelado detallado para los Bounded Contexts (IAM, Billing, Workspace, Discovery, Gateway). | +| 2.5 | 16/05/2026 | Salim Ramirez, Eric, Gutierrez Soto Jhosepmyr | Diseño UX/UI completo para la aplicación móvil y Landing Page (wireframes, wireflows y prototipos interactivos de alta fidelidad). Refactorización de justificaciones de restricciones e impacto arquitectónico, soporte al cliente en diagrama C4 y actualización final de Student Outcomes para TP. |
@@ -115,74 +118,110 @@ TB1: * [3.4. Impact Mapping](#34-impact-mapping) * [Capítulo IV: Strategic-Level Product Design](#capítulo-iv-strategic-level-product-design) * [4.1. Strategic-Level Attribute-Driven Design](#41-strategic-level-attribute-driven-design) - * [4.1.1. Design Purpose](#411-design-purpose) - * [4.1.2. Attribute-Driven Design Inputs](#412-attribute-driven-design-inputs) - * [4.1.2.1. Primary Functionality (Primary User Stories)](#4121-primary-functionality-primary-user-stories) - * [4.1.2.2. Quality attribute Scenarios](#4122-quality-attribute-scenarios) - * [4.1.2.3. Constraints](#4123-constraints) - * [4.1.3. Architectural Drivers Backlog](#413-architectural-drivers-backlog) - * [4.1.4. Architectural Design Decisions](#414-architectural-design-decisions) - * [4.1.5. Quality Attribute Scenario Refinements](#415-quality-attribute-scenario-refinements) - * [4.2. Strategic-Level Domain-Driven Design](#42-strategic-level-domain-driven-design) - * [4.2.1. EventStorming](#421-eventstorming) - * [4.2.2. Candidate Context Discovery](#422-candidate-context-discovery) - * [4.2.3. Domain Message Flows Modeling](#423-domain-message-flows-modeling) - * [4.2.4. Bounded Context Canvases](#424-bounded-context-canvases) - * [4.2.5. Context Mapping](#425-context-mapping) - * [4.3. Software Architecture](#43-software-architecture) - * [4.3.1. Software Architecture System Landscape Diagram](#431-software-architecture-system-landscape-diagram) - * [4.3.2. Software Architecture Context Level Diagrams](#432-software-architecture-context-level-diagrams) - * [4.3.3. Software Architecture Container Level Diagrams](#433-software-architecture-container-level-diagrams) - * [4.3.4. Software Architecture Deployment Diagrams](#434-software-architecture-deployment-diagrams) + * [4.1.1. Design Purpose](#411-design-purpose) + * [4.1.2. Attribute-Driven Design Inputs](#412-attribute-driven-design-inputs) + * [4.1.2.1. Primary Functionality (Primary User Stories)](#4121-primary-functionality-primary-user-stories) + * [4.1.2.2. Quality attribute Scenarios](#4122-quality-attribute-scenarios) + * [4.1.2.3. Constraints](#4123-constraints) + * [4.1.3. Architectural Drivers Backlog](#413-architectural-drivers-backlog) + * [4.1.4. Architectural Design Decisions](#414-architectural-design-decisions) + * [4.1.5. Quality Attribute Scenario Refinements](#415-quality-attribute-scenario-refinements) + * [4.2. Strategic-Level Domain-Driven Design](#42-strategic-level-domain-driven-design) + * [4.2.1. EventStorming](#421-eventstorming) + * [4.2.2. Candidate Context Discovery](#422-candidate-context-discovery) + * [4.2.3. Domain Message Flows Modeling](#423-domain-message-flows-modeling) + * [4.2.4. Bounded Context Canvases](#424-bounded-context-canvases) + * [4.2.5. Context Mapping](#425-context-mapping) + * [4.3. Software Architecture](#43-software-architecture) + * [4.3.1. Software Architecture System Landscape Diagram](#431-software-architecture-system-landscape-diagram) + * [4.3.2. Software Architecture Context Level Diagrams](#432-software-architecture-context-level-diagrams) + * [4.3.3. Software Architecture Container Level Diagrams](#433-software-architecture-container-level-diagrams) + * [4.3.4. Software Architecture Deployment Diagrams](#434-software-architecture-deployment-diagrams) * [Capítulo V: Tactical-Level Software Design](#capítulo-v-tactical-level-software-design) - * [5.X. Bounded Context: ](#5x-bounded-context-bounded-context-name) - * [5.X.1. Domain Layer](#5x1-domain-layer) - * [5.X.2. Interface Layer](#5x2-interface-layer) - * [5.X.3. Application Layer](#5x3-application-layer) - * [5.X.4. Infrastructure Layer](#5x4-infrastructure-layer) - * [5.X.6. Bounded Context Software Architecture Component Level Diagrams](#5x6-bounded-context-software-architecture-component-level-diagrams) - * [5.X.7. Bounded Context Software Architecture Code Level Diagrams](#5x7-bounded-context-software-architecture-code-level-diagrams) - * [5.X.7.1. Bounded Context Domain Layer Class Diagrams](#5x71-bounded-context-domain-layer-class-diagrams) - * [5.X.7.2. Bounded Context Database Design Diagram](#5x72-bounded-context-database-design-diagram) + * [5.1. Bounded Context: IAM](#51-bounded-context-iam) + * [5.1.1. Domain Layer](#511-domain-layer) + * [5.1.2. Interface Layer](#512-interface-layer) + * [5.1.3. Application Layer](#513-application-layer) + * [5.1.4. Infrastructure Layer](#514-infrastructure-layer) + * [5.1.6. Bounded Context Software Architecture Component Level Diagrams](#516-bounded-context-software-architecture-component-level-diagrams) + * [5.1.7. Bounded Context Software Architecture Code Level Diagrams](#517-bounded-context-software-architecture-code-level-diagrams) + * [5.1.7.1. Bounded Context Domain Layer Class Diagrams](#5171-bounded-context-domain-layer-class-diagrams) + * [5.1.7.2. Bounded Context Database Design Diagram](#5172-bounded-context-database-design-diagram) + * [5.2. Bounded Context: Billing and Subscriptions](#52-bounded-context-billing-and-subscriptions) + * [5.2.1. Domain Layer](#521-domain-layer) + * [5.2.2. Interface Layer](#522-interface-layer) + * [5.2.3. Application Layer](#523-application-layer) + * [5.2.4. Infrastructure Layer](#524-infrastructure-layer) + * [5.2.6. Bounded Context Software Architecture Component Level Diagrams](#526-bounded-context-software-architecture-component-level-diagrams) + * [5.2.7. Bounded Context Software Architecture Code Level Diagrams](#527-bounded-context-software-architecture-code-level-diagrams) + * [5.2.7.1. Bounded Context Domain Layer Class Diagrams](#5271-bounded-context-domain-layer-class-diagrams) + * [5.2.7.2. Bounded Context Database Design Diagram](#5272-bounded-context-database-design-diagram) + * [5.3. Bounded Context: Workspace Management](#53-bounded-context-workspace-management) + * [5.3.1. Domain Layer](#531-domain-layer) + * [5.3.2. Interface Layer](#532-interface-layer) + * [5.3.3. Application Layer](#533-application-layer) + * [5.3.4. Infrastructure Layer](#534-infrastructure-layer) + * [5.3.6. Bounded Context Software Architecture Component Level Diagrams](#536-bounded-context-software-architecture-component-level-diagrams) + * [5.3.7. Bounded Context Software Architecture Code Level Diagrams](#537-bounded-context-software-architecture-code-level-diagrams) + * [5.3.7.1. Bounded Context Domain Layer Class Diagrams](#5371-bounded-context-domain-layer-class-diagrams) + * [5.3.7.2. Bounded Context Database Design Diagram](#5372-bounded-context-database-design-diagram) + * [5.4. Bounded Context: Requirement Discovery](#54-bounded-context-requirement-discovery) + * [5.4.1. Domain Layer](#541-domain-layer) + * [5.4.2. Interface Layer](#542-interface-layer) + * [5.4.3. Application Layer](#543-application-layer) + * [5.4.4. Infrastructure Layer](#544-infrastructure-layer) + * [5.4.6. Bounded Context Software Architecture Component Level Diagrams](#546-bounded-context-software-architecture-component-level-diagrams) + * [5.4.7. Bounded Context Software Architecture Code Level Diagrams](#547-bounded-context-software-architecture-code-level-diagrams) + * [5.4.7.1. Bounded Context Domain Layer Class Diagrams](#5471-bounded-context-domain-layer-class-diagrams) + * [5.4.7.2. Bounded Context Database Design Diagram](#5472-bounded-context-database-design-diagram) + * [5.5. Bounded Context: Integration Gateway](#55-bounded-context-integration-gateway) + * [5.5.1. Domain Layer](#551-domain-layer) + * [5.5.2. Interface Layer](#552-interface-layer) + * [5.5.3. Application Layer](#553-application-layer) + * [5.5.4. Infrastructure Layer](#554-infrastructure-layer) + * [5.5.6. Bounded Context Software Architecture Component Level Diagrams](#556-bounded-context-software-architecture-component-level-diagrams) + * [5.5.7. Bounded Context Software Architecture Code Level Diagrams](#557-bounded-context-software-architecture-code-level-diagrams) + * [5.5.7.1. Bounded Context Domain Layer Class Diagrams](#5571-bounded-context-domain-layer-class-diagrams) + * [5.5.7.2. Bounded Context Database Design Diagram](#5572-bounded-context-database-design-diagram) * [Capítulo VI: Solution UX Design](#capítulo-vi-solution-ux-design) - * [6.1. Style Guidelines](#61-style-guidelines) - * [6.1.1. General Style Guidelines](#611-general-style-guidelines) - * [6.1.2. Web, Mobile & Devices Style Guidelines](#612-web-mobile--devices-style-guidelines) - * [6.2. Information Architecture](#62-information-architecture) - * [6.2.2. Labeling Systems](#622-labeling-systems) - * [6.2.3. Searching Systems](#623-searching-systems) - * [6.2.4. SEO Tags and Meta Tags](#624-seo-tags-and-meta-tags) - * [6.2.5. Navigation Systems](#625-navigation-systems) - * [6.3. Landing Page UI Design](#63-landing-page-ui-design) - * [6.3.1. Landing Page Wireframe](#631-landing-page-wireframe) - * [6.3.2. Landing Page Mock-up](#632-landing-page-mock-up) - * [6.4. Applications UX/UI Design](#64-applications-uxui-design) - * [6.4.1. Applications Wireframes](#641-applications-wireframes) - * [6.4.2. Applications Wireflow Diagrams](#642-applications-wireflow-diagrams) - * [6.4.2. Applications Mock-ups](#642-applications-mock-ups) - * [6.4.3. Applications User Flow Diagrams](#643-applications-user-flow-diagrams) - * [6.5. Applications Prototyping](#65-applications-prototyping) + * [6.1. Style Guidelines](#61-style-guidelines) + * [6.1.1. General Style Guidelines](#611-general-style-guidelines) + * [6.1.2. Web, Mobile & Devices Style Guidelines](#612-web-mobile--devices-style-guidelines) + * [6.2. Information Architecture](#62-information-architecture) + * [6.2.2. Labeling Systems](#622-labeling-systems) + * [6.2.3. Searching Systems](#623-searching-systems) + * [6.2.4. SEO Tags and Meta Tags](#624-seo-tags-and-meta-tags) + * [6.2.5. Navigation Systems](#625-navigation-systems) + * [6.3. Landing Page UI Design](#63-landing-page-ui-design) + * [6.3.1. Landing Page Wireframe](#631-landing-page-wireframe) + * [6.3.2. Landing Page Mock-up](#632-landing-page-mock-up) + * [6.4. Applications UX/UI Design](#64-applications-uxui-design) + * [6.4.1. Applications Wireframes](#641-applications-wireframes) + * [6.4.2. Applications Wireflow Diagrams](#642-applications-wireflow-diagrams) + * [6.4.2. Applications Mock-ups](#642-applications-mock-ups) + * [6.4.3. Applications User Flow Diagrams](#643-applications-user-flow-diagrams) + * [6.5. Applications Prototyping](#65-applications-prototyping) * [Capítulo VII: Product Implementation, Validation & Deployment](#capítulo-vii-product-implementation-validation--deployment) - * [7.1. Software Configuration Management](#71-software-configuration-management) - * [7.1.1. Software Development Environment Configuration](#711-software-development-environment-configuration) - * [7.1.2. Source Code Management](#712-source-code-management) - * [7.1.3. Source Code Style Guide & Conventions](#713-source-code-style-guide--conventions) - * [7.1.4. Software Deployment Configuration](#714-software-deployment-configuration) - * [7.2. Solution Implementation](#72-solution-implementation) - * [7.2.X. Sprint n](#72x-sprint-n) - * [7.2.X.1. Sprint Planning n](#72x1-sprint-planning-n) - * [7.2.X.2. Sprint Backlog n](#72x2-sprint-backlog-n) - * [7.2.X.3. Development Evidence for Sprint Review](#72x3-development-evidence-for-sprint-review) - * [7.2.X.4. Testing Suite Evidence for Sprint Review](#72x4-testing-suite-evidence-for-sprint-review) - * [7.2.X.5. Execution Evidence for Sprint Review](#72x5-execution-evidence-for-sprint-review) - * [7.2.X.6. Services Documentation Evidence for Sprint Review](#72x6-services-documentation-evidence-for-sprint-review) - * [7.2.X.7. Software Deployment Evidence for Sprint Review](#72x7-software-deployment-evidence-for-sprint-review) - * [7.2.X.8. Team Collaboration Insights during Sprint](#72x8-team-collaboration-insights-during-sprint) - * [7.3. Validation Interviews](#73-validation-interviews) - * [7.3.1. Diseño de Entrevistas](#731-diseño-de-entrevistas) - * [7.3.2. Registro de Entrevistas](#732-registro-de-entrevistas) - * [7.3.3. Evaluaciones según heurísticas](#733-evaluaciones-según-heurísticas) - * [7.4. Video About-the-Product](#74-video-about-the-product) + * [7.1. Software Configuration Management](#71-software-configuration-management) + * [7.1.1. Software Development Environment Configuration](#711-software-development-environment-configuration) + * [7.1.2. Source Code Management](#712-source-code-management) + * [7.1.3. Source Code Style Guide & Conventions](#713-source-code-style-guide--conventions) + * [7.1.4. Software Deployment Configuration](#714-software-deployment-configuration) + * [7.2. Solution Implementation](#72-solution-implementation) + * [7.2.X. Sprint n](#72x-sprint-n) + * [7.2.X.1. Sprint Planning n](#72x1-sprint-planning-n) + * [7.2.X.2. Sprint Backlog n](#72x2-sprint-backlog-n) + * [7.2.X.3. Development Evidence for Sprint Review](#72x3-development-evidence-for-sprint-review) + * [7.2.X.4. Testing Suite Evidence for Sprint Review](#72x4-testing-suite-evidence-for-sprint-review) + * [7.2.X.5. Execution Evidence for Sprint Review](#72x5-execution-evidence-for-sprint-review) + * [7.2.X.6. Services Documentation Evidence for Sprint Review](#72x6-services-documentation-evidence-for-sprint-review) + * [7.2.X.7. Software Deployment Evidence for Sprint Review](#72x7-software-deployment-evidence-for-sprint-review) + * [7.2.X.8. Team Collaboration Insights during Sprint](#72x8-team-collaboration-insights-during-sprint) + * [7.3. Validation Interviews](#73-validation-interviews) + * [7.3.1. Diseño de Entrevistas](#731-diseño-de-entrevistas) + * [7.3.2. Registro de Entrevistas](#732-registro-de-entrevistas) + * [7.3.3. Evaluaciones según heurísticas](#733-evaluaciones-según-heurísticas) + * [7.4. Video About-the-Product](#74-video-about-the-product) * [Conclusiones](#conclusiones) * [Bibliografía](#bibliografía) * [Anexos](#anexos) @@ -200,10 +239,10 @@ El curso contribuye al cumplimiento del Student Outcome ABET: En el siguiente cuadro se describen las acciones realizadas y los enunciados de conclusiones por parte del grupo, los cuales permiten sustentar el haber alcanzado el logro del ABET – EAC - Student Outcome 3. -| **Criterio específico** | **Acciones realizadas** | **Conclusiones** | -|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Comunica oralmente sus ideas y/o resultados con objetividad a público de diferentes especialidades y niveles jerárquicos, en el marco del desarrollo de un proyecto en ingeniería.** | **TB1**

**Gutiérrez Soto, Jhosepmyr Orlando – 202317638**
Trabajé comunicando oralmente los aspectos generales del proyecto relacionados con el Capítulo I y parte del Capítulo IV. Expliqué la descripción de la startup, la propuesta de solución y los fundamentos estratégicos del producto, procurando presentar las ideas de manera clara, ordenada y comprensible para una audiencia académica.

**Hernández Tuiro, Eric Ernesto – 20221C857**
Trabajé comunicando oralmente los contenidos vinculados al Capítulo I y Capítulo IV. Presenté ideas relacionadas con el perfil de la solución, el enfoque estratégico del producto y las decisiones generales de diseño, utilizando un lenguaje objetivo y adecuado para explicar la relación entre la problemática identificada y la solución propuesta.

**Ramirez Mestanza, Salim Ignacio – 20201E843**
Trabajé comunicando oralmente los avances desarrollados en el Capítulo II, principalmente los resultados del análisis de requerimientos, entrevistas, competidores y hallazgos del proceso de need finding. Mi participación permitió explicar cómo se identificaron necesidades relevantes para orientar el desarrollo del producto.

**Varela Bustinza, Marcelo Alessandro – 202319668**
Trabajé comunicando oralmente los resultados correspondientes al Capítulo II, especialmente el análisis competitivo, las estrategias frente a competidores, el diseño y análisis de entrevistas, así como los principales hallazgos obtenidos sobre los usuarios. Busqué explicar la información de manera objetiva, conectando los resultados con la definición de requerimientos del proyecto.

**Sulca Gonzales, Paul Fernando – 20221C486**
Trabajé comunicando oralmente los contenidos del Capítulo III, relacionados con la especificación de requerimientos, user stories, product backlog e impact mapping. Expliqué cómo los hallazgos obtenidos en etapas anteriores se transformaron en requisitos y elementos priorizados para el desarrollo de la solución. | Como equipo, se logró comunicar oralmente los avances del proyecto de manera clara, organizada y objetiva. Cada integrante explicó los resultados correspondientes a su participación, conectando los capítulos desarrollados con el propósito general de la solución. Asimismo, la exposición permitió adaptar el lenguaje técnico a una audiencia académica, integrando aspectos de negocio, usuarios, requerimientos y diseño de ingeniería. | -| **Comunica en forma escrita ideas y/o resultados con objetividad a público de diferentes especialidades y niveles jerárquicos, en el marco del desarrollo de un proyecto en ingeniería.** | **TB1**

**Gutiérrez Soto, Jhosepmyr Orlando – 202317638**
Trabajé comunicando de forma escrita los contenidos relacionados con el Capítulo I y parte del Capítulo IV. Redacté información sobre el perfil de la startup, la propuesta de solución y los elementos estratégicos del diseño del producto, procurando mantener una estructura clara y una redacción adecuada para el contexto académico y de ingeniería.

**Hernández Tuiro, Eric Ernesto – 20221C857**
Trabajé comunicando de forma escrita los apartados vinculados al Capítulo I y Capítulo IV. Mi aporte se centró en organizar y redactar ideas sobre la solución propuesta, su relación con la problemática y los criterios estratégicos del producto, asegurando coherencia entre el enfoque del proyecto y las decisiones de diseño.

**Ramirez Mestanza, Salim Ignacio – 20201E843**
Trabajé comunicando de forma escrita los contenidos del Capítulo II, principalmente en los apartados de análisis de requerimientos, entrevistas, competidores, identificación de necesidades y lenguaje ubicuo. Mi aporte permitió documentar los hallazgos de manera ordenada y orientada a sustentar la definición de requerimientos.

**Varela Bustinza, Marcelo Alessandro – 202319668**
Trabajé comunicando de forma escrita los resultados del Capítulo II, incluyendo el análisis competitivo, estrategias frente a competidores, diseño y análisis de entrevistas, user personas, mapas de empatía y escenarios actuales. Mi trabajo contribuyó a presentar evidencia relevante sobre las necesidades del usuario y su relación con los requerimientos del producto.

**Sulca Gonzales, Paul Fernando – 20221C486**
Trabajé comunicando de forma escrita los contenidos del Capítulo III, enfocados en to-be scenario mapping, user stories, product backlog e impact mapping. Mi aporte permitió transformar los hallazgos del análisis en requisitos claros, priorizados y alineados con los objetivos del producto. | Como equipo, se logró comunicar por escrito las ideas, resultados y decisiones del proyecto de manera estructurada y objetiva. El documento integra información sobre negocio, usuarios, requerimientos y diseño estratégico, manteniendo una secuencia lógica entre los capítulos. Además, la redacción permitió presentar el proyecto de forma comprensible para audiencias con distintos niveles de conocimiento técnico. | +| **Criterio específico** | **Acciones realizadas** | **Conclusiones** | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Comunica oralmente sus ideas y/o resultados con objetividad a público de diferentes especialidades y niveles jerárquicos, en el marco del desarrollo de un proyecto en ingeniería.** | **TB1**
**Gutiérrez Soto, Jhosepmyr Orlando – 202317638**
Trabajé comunicando oralmente los aspectos generales del proyecto relacionados con el Capítulo I y parte del Capítulo IV. Expliqué la descripción de la startup, la propuesta de solución y los fundamentos estratégicos del producto, procurando presentar las ideas de manera clara, ordenada y comprensible para una audiencia académica.

**Hernández Tuiro, Eric Ernesto – 20221C857**
Trabajé comunicando oralmente los contenidos vinculados al Capítulo I y Capítulo IV. Presenté ideas relacionadas con el perfil de la solución, el enfoque estratégico del producto y las decisiones generales de diseño, utilizando un lenguaje objetivo y adecuado para explicar la relación entre la problemática identificada y la solución propuesta.

**Ramirez Mestanza, Salim Ignacio – 20201E843**
Trabajé comunicando oralmente los avances desarrollados en el Capítulo II, principalmente los resultados del análisis de requerimientos, entrevistas, competidores y hallazgos del proceso de need finding. Mi participación permitió explicar cómo se identificaron necesidades relevantes para orientar el desarrollo del producto.

**Varela Bustinza, Marcelo Alessandro – 202319668**
Trabajé comunicando oralmente los resultados correspondientes al Capítulo II, especialmente el análisis competitivo, las estrategias frente a competidores, el diseño y análisis de entrevistas, así como los principales hallazgos obtenidos sobre los usuarios. Busqué explicar la información de manera objetiva, conectando los resultados con la definición de requerimientos del proyecto.

**Sulca Gonzales, Paul Fernando – 20221C486**
Trabajé comunicando oralmente los contenidos del Capítulo III, relacionados con la especificación de requerimientos, user stories, product backlog e impact mapping. Expliqué cómo los hallazgos obtenidos en etapas anteriores se transformaron en requisitos y elementos priorizados para el desarrollo de la solución.

**TP**
**Gutiérrez Soto, Jhosepmyr Orlando – 202317638**
Trabajé comunicando oralmente mis ideas con objetividad al presentar la arquitectura y diseño de cada bounded context en el Capítulo V. Adapté mi lenguaje para que perfiles tanto técnicos (desarrolladores) como de negocio (stakeholders) comprendieran cómo los componentes tácticos resuelven los problemas del proyecto de ingeniería.

**Hernández Tuiro, Eric Ernesto – 20221C857**
Trabajé comunicando oralmente los resultados de la refactorización aplicada al Event Storming y sustenté los diagramas del Capítulo V. Expuse estas ideas con objetividad, asegurándome de que las decisiones de diseño arquitectónico fueran claras para una audiencia de diferentes niveles jerárquicos y especialidades.

**Ramirez Mestanza, Salim Ignacio – 20201E843**
Trabajé comunicando oralmente las especificaciones de la landing page y el diseño y estilos de la versión mobile, correspondientes al Capítulo VI. Presenté mis ideas de forma objetiva, demostrando cómo las decisiones de interfaz de usuario se alinean con los requerimientos técnicos y de ingeniería.

**Varela Bustinza, Marcelo Alessandro – 202319668**
Comuniqué oralmente los resultados del diseño UX/UI de la aplicación web de Reqs-AI, explicando el flujo completo desde la autenticación y creación del workspace hasta la gestión de proyectos, sesiones de descubrimiento, revisión de historias de usuario, integración con Jira, facturación y configuración del equipo. Además, sustenté la relación entre wireframes, wireflows y mock-ups, destacando cómo cada pantalla mantiene coherencia visual, navegación consistente y alineación con las funcionalidades principales del producto.

**Sulca Gonzales, Paul Fernando – 20221C486**
Trabajé comunicando oralmente la refactorización de las User Stories y del Product Backlog, además de sustentar partes del Capítulo V. Mantuve objetividad al explicar cómo las historias guían el desarrollo de la ingeniería, asegurando que el público de diferentes especialidades entendiera su impacto en la arquitectura. | **TB1**
Como equipo, se logró comunicar oralmente los avances del proyecto de manera clara, organizada y objetiva. Cada integrante explicó los resultados correspondientes a su participación, conectando los capítulos desarrollados con el propósito general de la solución. Asimismo, la exposición permitió adaptar el lenguaje técnico a una audiencia académica, integrando aspectos de negocio, usuarios, requerimientos y diseño de ingeniería.

**TP**
Como equipo en esta etapa (TP), logramos sustentar oralmente decisiones arquitectónicas complejas y diseños de interfaz. Comunicamos con objetividad cómo el diseño táctico de los Bounded Contexts y las mejoras en UX/UI resuelven las necesidades del negocio. Adaptamos nuestra exposición para que tanto perfiles gerenciales (enfocados en el valor del producto) como técnicos (enfocados en patrones de ingeniería) comprendieran claramente la evolución y escalabilidad del sistema. | +| **Comunica en forma escrita ideas y/o resultados con objetividad a público de diferentes especialidades y niveles jerárquicos, en el marco del desarrollo de un proyecto en ingeniería.** | **TB1**
**Gutiérrez Soto, Jhosepmyr Orlando – 202317638**
Trabajé comunicando de forma escrita los contenidos relacionados con el Capítulo I y parte del Capítulo IV. Redacté información sobre el perfil de la startup, la propuesta de solución y los elementos estratégicos del diseño del producto, procurando mantener una estructura clara y una redacción adecuada para el contexto académico y de ingeniería.

**Hernández Tuiro, Eric Ernesto – 20221C857**
Trabajé comunicando de forma escrita los apartados vinculados al Capítulo I y Capítulo IV. Mi aporte se centró en organizar y redactar ideas sobre la solución propuesta, su relación con la problemática y los criterios estratégicos del producto, asegurando coherencia entre el enfoque del proyecto y las decisiones de diseño.

**Ramirez Mestanza, Salim Ignacio – 20201E843**
Trabajé comunicando de forma escrita los contenidos del Capítulo II, principalmente en los apartados de análisis de requerimientos, entrevistas, competidores, identificación de necesidades y lenguaje ubicuo. Mi aporte permitió documentar los hallazgos de manera ordenada y orientada a sustentar la definición de requerimientos.

**Varela Bustinza, Marcelo Alessandro – 202319668**
Trabajé comunicando de forma escrita los resultados del Capítulo II, incluyendo el análisis competitivo, estrategias frente a competidores, diseño y análisis de entrevistas, user personas, mapas de empatía y escenarios actuales. Mi trabajo contribuyó a presentar evidencia relevante sobre las necesidades del usuario y su relación con los requerimientos del producto.

**Sulca Gonzales, Paul Fernando – 20221C486**
Trabajé comunicando de forma escrita los contenidos del Capítulo III, enfocados en to-be scenario mapping, user stories, product backlog e impact mapping. Mi aporte permitió transformar los hallazgos del análisis en requisitos claros, priorizados y alineados con los objetivos del producto.

**TP**

**Gutiérrez Soto, Jhosepmyr Orlando – 202317638**
Trabajé comunicando en forma escrita los resultados del diseño táctico de cada bounded context en el Capítulo V. Redacté la estructura de las capas de dominio, aplicación e infraestructura manteniendo objetividad técnica, utilizando diagramas estándar para que equipos de diferentes especialidades puedan comprender la arquitectura.

**Hernández Tuiro, Eric Ernesto – 20221C857**
Trabajé comunicando en forma escrita las mejoras y la refactorización aplicadas al Event Storming, documentando objetivamente las decisiones tomadas. Apoyé en la redacción técnica del Capítulo V, asegurando que la documentación sea comprensible y útil tanto para desarrolladores como para evaluadores del proyecto.

**Ramirez Mestanza, Salim Ignacio – 20201E843**
Trabajé comunicando en forma escrita los elementos del Capítulo VI, especificando la landing page y el diseño y estilos de la versión mobile del app. Documenté estas decisiones de UX/UI con objetividad, estructurando la información de manera que sea fácilmente interpretable por equipos de distintas disciplinas.

**Varela Bustinza, Marcelo Alessandro – 202319668**
Comuniqué de forma escrita la documentación del Capítulo VI: Solution UX Design, desarrollando secciones relacionadas con style guidelines, information architecture, wireframes, wireflows y mock-ups de la web application. Redacté subtítulos y descripciones para cada imagen, organicé los recursos visuales con nombres consistentes y expliqué cómo cada pantalla representa una acción o estado funcional dentro del flujo de Reqs-AI. Este trabajo permitió evidenciar la evolución desde la estructura de baja fidelidad hasta la propuesta visual final de la plataforma.

**Sulca Gonzales, Paul Fernando – 20221C486**
Trabajé comunicando en forma escrita la refactorización de las User Stories y la actualización del Product Backlog. Redacté estos artefactos con objetividad técnica para que sirvan de puente claro entre las necesidades del negocio y la implementación en ingeniería, apoyando también en la documentación del Capítulo V. | **TB1**
Como equipo, se logró comunicar por escrito las ideas, resultados y decisiones del proyecto de manera estructurada y objetiva. El documento integra información sobre negocio, usuarios, requerimientos y diseño estratégico, manteniendo una secuencia lógica entre los capítulos. Además, la redacción permitió presentar el proyecto de forma comprensible para audiencias con distintos niveles de conocimiento técnico.

**TP**
Para el entregable TP, el equipo demostró la capacidad de documentar formalmente la arquitectura de software (Capítulo V) y el diseño de la experiencia de usuario (Capítulo VI). Se estructuraron los modelos de dominio, diagramas tácticos y wireflows con estricta objetividad técnica. Esta documentación escrita permite que profesionales de diferentes especialidades, desde desarrolladores hasta líderes de proyecto, puedan entender las soluciones de ingeniería propuestas sin ambigüedades. |
@@ -282,7 +321,6 @@ Nuestro enfoque inicial serán las Startups tecnológicas y empresas de desarrol Sabremos que hemos tenido éxito cuando observemos una reducción del 40% en las reuniones de seguimiento para aclaración de requisitos, una disminución significativa en el tiempo que el analista dedica al postprocesamiento de la información y una tasa de aceptación de historias de usuario superior al 80% en la primera iteración de revisión con el equipo de desarrollo. - #### 1.2.2.2. Lean UX Assumptions Esta sección presenta las suposiciones fundamentales del proyecto Reqs-AI, estructuradas bajo el marco de Lean UX. Aquí definimos los resultados de negocio esperados, los perfiles de usuario que enfrentan el problema y los beneficios tangibles que estos obtendrán al utilizar nuestra solución. @@ -757,7 +795,6 @@ Los user personas sintetizan patrones de comportamiento, objetivos, frustracione User Persona Analista de Sistemas Enterprise - ### 2.3.2. User Task Matrix En este User Task Matrix se detallan las tareas que realizan los dos segmentos objetivos considerados en Reqs-AI: el Líder Técnico de Startup y el Analista de Sistemas Enterprise. Las tareas descritas corresponden al trabajo real de levantamiento, validación y transferencia de requisitos, y se ejecutan independientemente de la existencia de una herramienta de software. @@ -776,7 +813,6 @@ En este User Task Matrix se detallan las tareas que realizan los dos segmentos o | Revisar grabaciones/minutas y depurar documentación para cerrar vacíos de información | sometimes | medium | always | high | | Coordinar reuniones de seguimiento por dudas o contradicciones detectadas después del levantamiento | sometimes | medium | sometimes | high | - La principal diferencia está en el enfoque operativo: el Líder Técnico de Startup prioriza velocidad de ejecución y alineación práctica para codificar cuanto antes, mientras que el Analista de Sistemas Enterprise prioriza estandarización, trazabilidad y control de riesgo. Por ello, en el segmento enterprise aumentan la frecuencia e importancia de actividades formales como redacción estructurada, revisión exhaustiva de evidencias y gestión de cambios bajo criterios de gobernanza. ### 2.3.3. User Journey Mapping @@ -864,87 +900,116 @@ El escenario To-Be propone sesiones de levantamiento con mayor validación en ti El siguiente inventario detalla las funcionalidades del sistema. Nótese la diferenciación entre **US** (User Stories - Funcionalidades de interfaz/usuario) y **TS** (Technical Stories - API y Arquitectura backend). -| ID | Título de la Historia | Descripción (Como... quiero... para...) | Criterios de Aceptación (BDD) | Épica Asociada | -|:---------|:------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------| -| **EP00** | **Landing Page y Captación de Leads** | Landing page pública orientada a convertir visitantes en usuarios mediante la exposición de la propuesta de valor y beneficios por segmentos B2B. | -- | -- | -| US01 | Visualizar Propuesta de Valor (Hero) | Como visitante, quiero entender inmediatamente qué es Reqs-AI y su propuesta de valor principal en la cabecera (Hero), para decidir en los primeros segundos si me interesa continuar explorando la herramienta. | Feature: Landing Page - Hero Section

Scenario: Carga inicial y visibilidad de conversión
Given un visitante que accede a la URL principal de Reqs-AI
When la página carga completamente
Then visualiza un título claro sobre la automatización de requisitos con IA
And un llamado a la acción (CTA) principal visible sin hacer scroll para iniciar el registro. | EP00 | -| US02 | Explorar Casos de Uso por Segmento | Como visitante, quiero alternar entre diferentes perfiles (ej. Consultoras vs Startups) en una sección interactiva, para ver beneficios y ejemplos específicos que se adapten a la realidad de mi equipo. | Feature: Landing Page - Segmentación Interactiva

Scenario Outline: Alternar entre perfiles de usuario
Given un visitante en la sección de 'Construido para tu equipo'
When hace clic en la pestaña del perfil <Perfil>
Then el contenido dinámico se actualiza sin recargar la página
And muestra el beneficio principal <Beneficio> asociado a ese perfil

Examples:
PerfilBeneficio
Consultoras de SoftwareReducción de horas facturables en análisis y toma de requerimientos.
Product Managers / StartupsIntegración directa con Jira y adopción de metodologías ágiles.
| EP00 | -| US03 | Validar Credibilidad (Social Proof) | Como visitante escéptico, quiero ver testimonios, métricas de éxito o logos de empresas que ya usan la herramienta, para sentir confianza antes de invertir mi tiempo o dinero en la plataforma. | Feature: Landing Page - Social Proof

Scenario: Visualización de respaldo social
Given un visitante explorando los beneficios de la herramienta
When llega a la sección de confianza (Social Proof)
Then el sistema le presenta un carrusel de testimonios o métricas de horas ahorradas
And no permite avanzar hacia el final sin antes exponer estas validaciones de mercado. | EP00 | -| US04 | Visualizar Planes y Precios | Como visitante evaluando la viabilidad financiera, quiero comparar los planes de suscripción (Gratuito, Pro, Equipo) de forma transparente, para determinar cuál se ajusta a mi presupuesto antes de crearme una cuenta. | Feature: Landing Page - Pricing

Scenario: Comparativa de planes (Toggle Mensual/Anual)
Given un visitante en la sección de Precios
When alterna el interruptor entre facturación 'Mensual' y 'Anual'
Then los precios de los planes de pago se actualizan dinámicamente mostrando el descuento aplicado
And cada tarjeta de plan contiene un CTA que redirige al formulario de registro correspondiente. | EP00 | -| **EP01** | **Autenticación y Seguridad** | Gestión de acceso y protección de identidad para los usuarios de Reqs-AI, garantizando que solo personal autorizado acceda a la plataforma y su historial. | -- | -- | -| US03 | Registro de cuenta | Como visitante que llega por primera vez a la plataforma, quiero crear una cuenta con mi correo y una contraseña, para poder acceder a mis proyectos y sesiones de captura de requisitos. | Feature: Registro de cuenta

Scenario: Registro exitoso (Happy Path)
Given un visitante en la página de registro
When ingresa un correo válido y una contraseña segura
Then el sistema crea la cuenta en estado 'pendiente de verificación'
And envía un correo con el enlace de activación

Scenario Outline: Validaciones de registro (Unhappy Paths)
Given un visitante en la página de registro
When ingresa datos con el problema <Problema>
Then el sistema rechaza el registro indicando <Mensaje>

Examples:
ProblemaMensaje
El correo ya está registrado en otra cuentaEl correo ingresado ya se encuentra en uso.
La contraseña tiene menos de 8 caracteresLa contraseña debe tener al menos 8 caracteres.
El formato del correo es inválidoIngresa un correo electrónico válido.
| EP01 | -| US04 | Verificación de correo | Como usuario con cuenta pendiente de activación, quiero verificar mi correo haciendo clic en el enlace que recibí, para activar mi cuenta y empezar a trabajar en la plataforma. | Feature: Verificación de correo

Scenario: Verificación exitosa (Happy Path)
Given un usuario con cuenta pendiente de activación
When hace clic en el enlace de verificación recibido por correo
Then el sistema activa la cuenta
And redirige al usuario al inicio de sesión

Scenario: Enlace expirado o inválido (Unhappy Path)
Given un usuario con un enlace de verificación
When el enlace fue usado previamente o ya expiró su vigencia
Then el sistema muestra un error de enlace inválido
And ofrece la opción de reenviar un nuevo correo de verificación | EP01 | -| US05 | Inicio de sesión | Como usuario con cuenta activa, quiero iniciar sesión con mi correo y contraseña, para acceder a mis proyectos y al historial de sesiones de mi organización. | Feature: Inicio de sesión

Scenario: Autenticación exitosa (Happy Path)
Given un usuario con cuenta activa
When ingresa sus credenciales correctas
Then el sistema le concede acceso
And lo redirige al panel principal de su última organización activa

Scenario Outline: Fallos de autenticación (Unhappy Paths)
Given un usuario intentando iniciar sesión
When ocurre la situación <Situacion>
Then el sistema deniega el acceso con el mensaje <Error>

Examples:
SituacionError
Contraseña incorrectaCredenciales inválidas.
La cuenta aún no ha sido verificadaDebes verificar tu correo antes de iniciar sesión.
Demasiados intentos fallidos consecutivosCuenta bloqueada temporalmente por seguridad.
| EP01 | -| US06 | Recuperación de contraseña | Como usuario registrado que no recuerda su contraseña, quiero recibir un enlace de restablecimiento en mi correo, para recuperar el acceso a mi cuenta sin perder mi historial de trabajo. | Feature: Recuperación de contraseña

Scenario: Solicitar recuperación (Happy Path)
Given un usuario que olvidó su contraseña
When ingresa su correo en el formulario de recuperación
Then el sistema envía un enlace de restablecimiento
And muestra un mensaje genérico de confirmación por seguridad

Scenario: Prevenir enumeración de usuarios (Edge Case - Seguridad)
Given un visitante malintencionado
When ingresa un correo que no existe en el sistema
Then el sistema no revela que el correo es inexistente
And muestra el mismo mensaje genérico de confirmación | EP01 | -| **EP02** | **Organizaciones** | Gestión de espacios de trabajo independientes para separar la información de diferentes empresas o equipos, garantizando que cada organización acceda únicamente a sus propios datos. | -- | -- | -| US07 | Crear organización | Como usuario autenticado que aún no pertenece a ninguna organización, quiero crear un espacio de trabajo con el nombre de mi empresa, para centralizar mis proyectos y gestionar mi equipo en un entorno separado. | Feature: Crear organización

Scenario: Creación exitosa (Happy Path)
Given un usuario autenticado sin organización
When crea una organización con un nombre válido
Then el sistema genera el espacio de trabajo
And asigna al usuario el rol inamovible de 'Propietario'

Scenario: Nombre de organización vacío (Unhappy Path)
Given un usuario creando una organización
When deja el nombre en blanco
Then el sistema bloquea la creación exigiendo un nombre obligatorio | EP02 | -| US32 | Editar organización | Como Propietario de la organización, quiero actualizar el nombre o los datos generales de mi organización, para mantener la información correcta cuando el equipo o el negocio cambia. | Feature: Editar organización

Scenario: Actualizar datos (Happy Path)
Given el Propietario de una organización
When modifica el nombre y guarda los cambios
Then la organización actualiza sus datos en toda la plataforma

Scenario: Intento de edición sin permisos (Unhappy Path)
Given un miembro regular de la organización
When intenta acceder a la configuración de la organización
Then el sistema oculta o bloquea la opción por falta de privilegios | EP02 | -| US33 | Cambiar de organización | Como usuario que pertenece a más de una organización, quiero seleccionar con cuál quiero trabajar desde el menú principal, para asegurarme de estar operando en el contexto correcto según el cliente que estoy atendiendo. | Feature: Cambiar de organización

Scenario: Cambio exitoso de contexto (Happy Path)
Given un usuario que pertenece a múltiples organizaciones
When selecciona una organización distinta desde el menú
Then el sistema recarga el contexto de trabajo
And muestra únicamente los proyectos de la organización seleccionada

Scenario: Eliminación concurrente (Edge Case)
Given un usuario intentando cambiar a la Organización B
When el propietario de la Organización B lo elimina en ese mismo instante
Then el sistema deniega el acceso y lo devuelve a su organización actual | EP02 | -| US41 | Política de retención de audios | Como Propietario de la organización, quiero configurar la eliminación automática de los archivos de audio originales X días después de procesarse, para cumplir con las políticas de privacidad y confidencialidad corporativas. | Feature: Política de retención de audios

Scenario: Eliminación automática al cumplir plazo (Happy Path)
Given una organización con la retención configurada en 7 días
When se cumple el plazo desde que un audio fue procesado
Then el sistema elimina permanentemente el archivo de audio físico
And conserva únicamente las historias generadas en texto

Scenario: Intentar acceder a un audio eliminado (Unhappy Path)
Given un audio que ya superó su periodo de retención y fue purgado
When un usuario intenta reproducirlo o descargarlo desde el historial
Then el sistema muestra un aviso de que el archivo fue eliminado por políticas de privacidad corporativa | EP02 | -| **EP03** | **Suscripción y Facturación** | Gestión de planes y límites de uso del sistema basados en un modelo de suscripción SaaS. Épica de baja prioridad — no entra en los primeros sprints. | -- | -- | -| US42 | Plan gratuito automático | Como usuario que acaba de crear su organización, quiero empezar a usar la plataforma sin ingresar datos de pago, para evaluar si se ajusta a mis necesidades antes de decidir suscribirme. | Feature: Plan gratuito automático

Scenario: Asignación por defecto (Happy Path)
Given un usuario que acaba de crear una nueva organización
When ingresa a su panel por primera vez
Then el sistema le asigna el 'Plan Gratuito'
And habilita las cuotas iniciales sin solicitar método de pago | EP03 | -| US43 | Suscripción al plan Pro | Como Propietario de una organización en plan gratuito, quiero contratar el plan Pro, para acceder a sesiones ilimitadas, captura en tiempo real e integración con Jira. | Feature: Suscripción al plan Pro

Scenario: Upgrade de plan exitoso (Happy Path)
Given el Propietario de una organización en plan gratuito
When ingresa un método de pago válido y contrata el Plan Pro
Then los límites de la organización se expanden inmediatamente
And se emite la factura correspondiente

Scenario: Tarjeta rechazada (Unhappy Path)
Given un Propietario intentando hacer upgrade
When la pasarela de pagos rechaza la tarjeta por fondos insuficientes
Then el sistema mantiene el plan gratuito
And notifica el error de pago al usuario | EP03 | -| US44 | Suscripción al plan Equipo | Como Propietario de una organización en plan Pro, quiero contratar el plan Equipo, para poder agregar a todos los miembros de mi equipo y definir mediante roles personalizados qué puede hacer cada uno. | Feature: Suscripción al plan Equipo

Scenario: Upgrade a Equipo (Happy Path)
Given el Propietario de una organización en Plan Pro
When contrata el Plan Equipo
Then se habilitan las funciones de gestión avanzada de roles y miembros múltiples | EP03 | -| US45 | Cancelación de suscripción | Como Propietario de una suscripción activa, quiero cancelar mi plan antes de que inicie el siguiente período de facturación, para no recibir cargos adicionales después de dejar de usar el servicio. | Feature: Cancelación de suscripción

Scenario: Cancelar antes de renovación (Happy Path)
Given el Propietario de una suscripción paga activa
When cancela la suscripción desde el panel
Then el sistema desactiva la auto-renovación
And mantiene los beneficios premium hasta el final del ciclo de facturación actual | EP03 | -| US46 | Ver estado del plan y consumo | Como Propietario de la organización, quiero ver el plan activo, la fecha de renovación y cuántas sesiones o proyectos he usado, para saber cuándo necesito actualizar mi plan antes de quedarme sin cupo. | Feature: Ver estado del plan y consumo

Scenario: Visualización de cuotas (Happy Path)
Given el Propietario de la organización
When ingresa a la sección de facturación
Then visualiza una barra de progreso con el consumo actual de sesiones frente al límite de su plan

Scenario: Límite alcanzado (Edge Case)
Given una organización que alcanzó exactamente el 100% de su límite
When el usuario visualiza el estado
Then el sistema muestra una alerta destacada invitando al upgrade | EP03 | -| US47 | Dashboard de valor y ahorro de tiempo | Como Propietario de la organización, quiero ver un panel que traduzca las historias generadas en 'horas de trabajo manual ahorradas', para justificar el pago de la suscripción y entender el retorno de inversión mensual de la herramienta. | Feature: Dashboard de ROI

Scenario: Calcular ROI exitosamente (Happy Path)
Given una organización con proyectos y sesiones generadas
When el administrador ingresa al dashboard principal
Then el sistema calcula el tiempo humano ahorrado en base a las horas de audio procesadas vs redacción manual
And muestra gráficas de productividad y métricas clave de retorno de inversión

Scenario: Organización sin historial (Unhappy Path)
Given una organización recién creada sin sesiones activas
When el administrador visita el dashboard
Then el sistema muestra un estado vacío con un mensaje orientativo sobre cómo comenzar a capturar valor | EP03 | -| **EP04** | **Gestión de Proyectos** | Creación, configuración y ciclo de vida de los proyectos por cliente, incluyendo la carga de conocimiento previo que permite a la IA generar historias más precisas. | -- | -- | -| US08 | Crear proyecto | Como miembro con permiso para crear proyectos, quiero registrar un proyecto asociado a un cliente específico, para organizar y separar todas las sesiones de levantamiento de requisitos de ese cliente. | Feature: Crear proyecto

Scenario: Creación de proyecto válida (Happy Path)
Given un usuario con permisos de creación
When crea un proyecto indicando el nombre del cliente
Then el proyecto aparece en el listado activo de la organización

Scenario: Proyecto duplicado (Unhappy Path)
Given un usuario creando un proyecto
When ingresa un nombre que ya existe activo en la misma organización
Then el sistema sugiere usar un nombre distinto para evitar confusiones | EP04 | -| US10 | Cargar documentos del cliente | Como miembro con permiso para configurar proyectos, quiero subir documentos y glosarios del cliente al proyecto, para que las historias generadas reflejen el vocabulario y las reglas del negocio de ese cliente de forma observable en cada historia producida. | Feature: Cargar documentos del cliente

Scenario: Carga de contexto exitosa (Happy Path)
Given un usuario configurando un proyecto
When sube un glosario en PDF válido
Then el sistema procesa el documento y lo incorpora a la base de conocimiento de la IA para futuras sesiones

Scenario Outline: Restricciones de carga documental (Unhappy Paths)
Given un usuario intentando subir documentos de contexto
When ocurre el escenario <Fallo>
Then el sistema rechaza el archivo indicando <Error>

Examples:
FalloError
El archivo es un ejecutable (.exe)Solo se permiten documentos de texto o PDF.
El archivo excede los 50MBEl documento es demasiado pesado.
| EP04 | -| US11 | Configurar perfil técnico del proyecto | Como miembro con permiso para configurar proyectos, quiero registrar las tecnologías que usa el equipo del cliente y los tipos de usuarios del sistema, para que los criterios de aceptación generados sean aplicables al contexto real del proyecto. | Feature: Configurar perfil técnico del proyecto

Scenario: Guardar perfil técnico (Happy Path)
Given un usuario con permisos en el proyecto
When define que el stack es 'React + Node' y guarda
Then el sistema utiliza esta instrucción como prompt base para la generación de criterios técnicos en las historias de usuario | EP04 | -| US30 | Editar proyecto | Como miembro con permiso para administrar proyectos, quiero modificar el nombre o la descripción de un proyecto existente, para corregir o actualizar la información cuando cambian los acuerdos con el cliente. | Feature: Editar proyecto

Scenario: Modificar datos del proyecto (Happy Path)
Given un usuario administrador del proyecto
When cambia la descripción del proyecto
Then los cambios se reflejan inmediatamente en el panel del equipo | EP04 | -| US31 | Archivar proyecto | Como miembro con permiso para administrar proyectos, quiero archivar un proyecto cuando termina el trabajo con ese cliente, para mantener el panel principal ordenado sin eliminar el historial de sesiones e historias generadas. | Feature: Archivar proyecto

Scenario: Archivar proyecto finalizado (Happy Path)
Given un proyecto activo
When el administrador lo archiva
Then el proyecto se oculta de las vistas principales pero mantiene su historial intacto

Scenario: Operaciones en proyecto archivado (Edge Case)
Given un proyecto en estado archivado
When un usuario intenta iniciar una nueva sesión de captura
Then el sistema bloquea la acción hasta que el proyecto sea desarchivado | EP04 | -| US09 | Proyecto de demostración automático | Como nuevo usuario que acaba de registrarse, quiero encontrar un proyecto de demostración pre-cargado con audios e historias ya generadas, para entender inmediatamente el valor de la plataforma sin tener que grabar mi propia reunión primero. | Feature: Proyecto Sandbox de Demo

Scenario: Cargar datos de demostración en nueva cuenta (Happy Path)
Given un usuario que acaba de registrarse exitosamente en la plataforma
When accede a su organización por primera vez
Then el sistema genera e inserta un proyecto de demostración pre-poblado (con audios, historias y transcripciones de ejemplo)
And lo marca visualmente como 'Sandbox / Demo' para que el usuario experimente de inmediato

Scenario: Restaurar proyecto de demostración (Flujo Alternativo)
Given un usuario que eliminó su proyecto de demostración
When hace clic en 'Restaurar datos de prueba' desde la configuración
Then el sistema vuelve a clonar el proyecto de ejemplo en su espacio de trabajo | EP04 | -| **EP05** | **Colaboración y Roles** | Gestión de roles personalizados con permisos configurables y administración de los miembros del equipo dentro de la organización y sus proyectos. El Propietario es el único rol fijo del sistema — es quien creó la organización, tiene todos los permisos y es el responsable del contrato. Todos los demás roles son creados y configurados por el Propietario. | -- | -- | -| US34 | Invitar miembro a la organización | Como miembro con permiso para gestionar el equipo, quiero invitar a un colega mediante su correo electrónico, para que pueda acceder a los proyectos que le correspondan dentro de la organización. | Feature: Invitar miembro

Scenario: Enviar invitación (Happy Path)
Given un usuario con permisos de gestión de equipo
When envía una invitación a un correo válido
Then el invitado recibe el correo con el enlace de acceso
And aparece en estado 'Pendiente' en el panel

Scenario Outline: Fallos de invitación (Unhappy Paths)
Given un administrador invitando a un usuario
When comete el error <Error_Inv>
Then la invitación falla indicando <Aviso>

Examples:
Error_InvAviso
El correo ya pertenece a la organizaciónEl usuario ya es miembro del equipo.
El correo ya tiene una invitación pendienteYa existe una invitación enviada a este correo.
| EP05 | -| US35 | Crear rol personalizado | Como Propietario de la organización, quiero crear un rol con un nombre definido por mi equipo, para representar una función real dentro de la organización en lugar de usar etiquetas genéricas del sistema. | Feature: Crear rol personalizado

Scenario: Rol creado exitosamente (Happy Path)
Given el Propietario de la organización
When crea el rol 'QA Lead'
Then el nuevo rol queda disponible para ser asignado a cualquier miembro | EP05 | -| US36 | Asignar permisos a un rol | Como Propietario de la organización, quiero seleccionar qué acciones puede realizar cada rol dentro de los proyectos y la organización, para que el nivel de acceso de cada miembro refleje exactamente sus responsabilidades reales. | Feature: Asignar permisos a un rol

Scenario: Configurar permisos (Happy Path)
Given el Propietario configurando un rol
When activa los permisos de 'Crear Sesiones' y 'Aprobar Historias'
Then todos los usuarios con ese rol adquieren dichas capacidades inmediatamente | EP05 | -| US37 | Asignar rol a un miembro | Como miembro con permiso para gestionar el equipo, quiero asignar uno de los roles disponibles a un miembro de la organización, para que sus permisos queden definidos en el momento en que acepta la invitación. Depende de: US19 y US20. | Feature: Asignar rol a un miembro

Scenario: Cambio de rol (Happy Path)
Given un administrador de equipo
When cambia el rol del Usuario A de 'Lector' a 'Editor'
Then el Usuario A obtiene acceso a las herramientas de edición en su próxima navegación | EP05 | -| US38 | Editar permisos de un rol existente | Como Propietario de la organización, quiero modificar los permisos de un rol ya creado, para ajustar el nivel de acceso de todos los miembros que lo tienen asignado cuando cambian sus responsabilidades. | Feature: Editar permisos de un rol existente

Scenario: Propagación de permisos (Happy Path)
Given un rol asignado a 5 usuarios
When el Propietario revoca el permiso de 'Exportar'
Then los 5 usuarios pierden la capacidad de exportar instantáneamente sin necesidad de re-login | EP05 | -| US39 | Eliminar un rol | Como Propietario de la organización, quiero eliminar un rol que ya no corresponde a ninguna función activa del equipo, para mantener la lista de roles ordenada y evitar asignaciones incorrectas. | Feature: Eliminar un rol

Scenario: Eliminar rol sin uso (Happy Path)
Given un rol personalizado que no tiene usuarios asignados
When el Propietario lo elimina
Then el rol desaparece definitivamente de la organización

Scenario: Intento de eliminar rol en uso (Unhappy Path)
Given un rol que está asignado a al menos un miembro
When el Propietario intenta eliminarlo
Then el sistema bloquea la eliminación
And exige reasignar a esos miembros a otro rol antes de proceder | EP05 | -| US40 | Remover miembro de la organización | Como miembro con permiso para gestionar el equipo, quiero remover a un miembro de la organización, para revocar su acceso a todos los proyectos de forma inmediata cuando ya no forma parte del equipo. | Feature: Remover miembro de la organización

Scenario: Remoción exitosa (Happy Path)
Given un administrador de equipo
When remueve al Usuario B de la organización
Then el Usuario B pierde acceso inmediatamente a todos los proyectos de esa organización

Scenario: Intento de remover al Propietario (Edge Case - Seguridad)
Given un administrador de equipo
When intenta remover al Propietario original de la cuenta
Then el sistema bloquea la acción indicando que el rol de Propietario es intransferible e irremovible | EP05 | -| **EP06** | **Captura en Tiempo Real** | Funcionalidad principal del producto que procesa audio en vivo para generar historias de usuario de forma automática mientras la reunión con el cliente ocurre. Las sesiones requieren un proyecto existente. | -- | -- | -| US32 | Iniciar sesión de captura en vivo | Como miembro con permiso para crear sesiones, quiero activar la captura de audio durante la reunión dentro de un proyecto existente, para que el sistema identifique quién habla en cada momento y genere las historias a partir de lo que dice el cliente sin que yo tenga que tomar notas. Depende de: US13. | Feature: Captura en vivo

Scenario: Iniciar captura exitosamente (Happy Path)
Given que el usuario tiene permisos y el proyecto es válido
When inicia la captura de audio en vivo
Then la sesión cambia a estado 'activa' y comienza a registrar el habla

Scenario Outline: Validaciones al intentar iniciar captura (Unhappy Paths)
Given que el usuario intenta iniciar una sesión de captura en vivo
When ocurre la situación <Condicion>
Then el sistema impide el inicio de la sesión
And muestra el mensaje <Mensaje_Error>

Examples:
CondicionMensaje_Error
El navegador no tiene permisos de micrófonoDebes otorgar permisos de micrófono para continuar.
El usuario tiene un rol sin permisos de creaciónNo tienes permisos para crear sesiones en este proyecto.
El proyecto seleccionado se encuentra archivadoEl proyecto está archivado y no admite nuevas sesiones.

Scenario: Pérdida de conexión al iniciar (Unhappy Path - Flujo Alternativo)
Given que el usuario inicia la captura
When se pierde la conexión a internet antes de confirmar con el servidor
Then el sistema cancela la creación de la sesión
And notifica al usuario que verifique su conexión | EP06 | -| US33 | Generación automática de historias | Como analista en sesión activa, quiero que el sistema convierta automáticamente lo que dice el cliente en historias de usuario con criterios de aceptación, para obtener un backlog estructurado al finalizar la reunión sin necesidad de documentar después. | Feature: Generación automática de historias

Scenario: Extraer historia desde un requisito claro (Happy Path)
Given una sesión activa
When el sistema detecta un requisito válido en la intervención del cliente
Then genera una historia de usuario con criterios de aceptación
And la agrega al backlog de la sesión en tiempo real

Scenario: Manejo de audio incomprensible o ruido (Unhappy Path)
Given una sesión activa
When el cliente habla de temas irrelevantes o hay ruido de fondo
Then el sistema ignora la intervención
And no genera historias vacías ni interrumpe la captura

Scenario: Intervención larga con múltiples requisitos (Edge Case)
Given una sesión activa
When el cliente menciona varios requerimientos distintos en una sola intervención continua
Then el sistema separa lógicamente los temas
And crea una historia individual por cada requerimiento detectado | EP06 | -| US19 | Descartar historia (Componente Unificado) | Como analista, quiero descartar una historia generada indicando un motivo, tanto durante la sesión en vivo como en la etapa de revisión posterior, utilizando una misma interfaz para mantener el backlog limpio. | Feature: Descarte de historias en vivo

Scenario: Descartar historia correctamente (Happy Path)
Given una historia recién generada en el backlog de la sesión
When el analista la descarta ingresando un motivo válido
Then la historia se oculta del backlog activo
And queda registrada en el historial con su motivo

Scenario Outline: Validaciones al descartar (Unhappy Paths)
Given que el analista intenta descartar una historia
When el formulario presenta el problema <Problema>
Then el sistema bloquea el descarte
And solicita la corrección con el mensaje <Mensaje>

Examples:
ProblemaMensaje
El campo de motivo está completamente vacíoDebes ingresar un motivo para descartar la historia.
El motivo excede el límite de caracteres (ej. 500)El motivo es demasiado largo.

Scenario: Descarte concurrente (Unhappy Path - Flujo Alternativo)
Given una historia visible en pantalla
When el analista intenta descartarla pero otro usuario ya lo hizo segundos antes
Then el sistema alerta que el estado de la historia ha cambiado
And refresca la vista del backlog | EP06 | -| US15 | Pausar y reanudar captura | Como analista durante una sesión activa, quiero pausar la captura cuando la conversación se desvía del tema o hay un receso, para evitar que se generen historias a partir de conversaciones que no son requisitos del cliente. | Feature: Pausa y reanudación

Scenario: Pausar y reanudar la captura (Happy Path)
Given una sesión en estado activa
When el analista pausa la sesión
Then el sistema detiene temporalmente la generación de historias
And al reanudarla, continúa el procesamiento normalmente

Scenario: Intentar pausar una sesión ya pausada (Unhappy Path de concurrencia)
Given una sesión que fue pausada por el analista A
When el analista B intenta pausarla desde otro dispositivo sin haber refrescado
Then el sistema ignora la acción
And sincroniza el estado de la UI para el analista B informando la pausa | EP06 | -| US16 | Cerrar y guardar sesión | Como analista al terminar una reunión, quiero finalizar la sesión de captura, para asegurar que todas las historias generadas queden guardadas en el historial del proyecto. | Feature: Cierre de sesión

Scenario: Cerrar sesión con historias guardadas (Happy Path)
Given una sesión activa con al menos una historia generada
When el analista cierra la sesión de captura
Then la sesión pasa a estado 'cerrada'
And todas las historias se persisten en el proyecto principal

Scenario: Prevenir pérdida de datos al cerrar (Unhappy Path - Flujo de Red)
Given una sesión activa con historias sin sincronizar al backend
When el usuario intenta cerrar la sesión pero no hay conexión a internet
Then el sistema bloquea el cierre temporalmente
And muestra una advertencia de sincronización pendiente para evitar pérdida de datos

Scenario: Cerrar sesión vacía (Edge Case)
Given una sesión activa en la que no se generó ninguna historia
When el analista cierra la sesión
Then el sistema permite el cierre
And muestra el resumen final con contadores en cero | EP06 | -| US18 | Abortar sesión sin guardar | Como analista en una sesión de captura, quiero poder abortar y descartar la reunión por completo si hubo un error (ej. reunión cancelada), para no ensuciar el historial del proyecto con datos basura. | Feature: Abortar sesión en vivo

Scenario: Cancelar y purgar sesión (Happy Path)
Given una sesión activa que no debe ser guardada
When el analista selecciona la opción 'Abortar y salir'
And confirma la advertencia destructiva
Then el sistema purga todas las historias generadas temporalmente
And cierra la sesión sin dejar rastro en el historial del proyecto

Scenario: Abortar por error sin confirmación (Unhappy Path)
Given una sesión activa
When el analista hace clic en 'Abortar'
Then el sistema muestra un modal de confirmación estricto pidiendo tipear 'ELIMINAR'
And no ejecuta el borrado hasta validar el input | EP06 | -| US13 | Etiquetar voz del cliente (Diarización) | Como analista revisando una sesión, quiero poder identificar y etiquetar qué voz corresponde al 'Cliente' y cuáles al 'Equipo', para que la IA priorice las necesidades del cliente y no genere historias basadas en nuestras propias preguntas. | Feature: Diarización (Etiquetado de locutor)

Scenario: Distinguir locutores automáticamente (Happy Path)
Given una sesión de grabación con múltiples participantes (ej. 2 clientes y 1 analista)
When el motor procesa el audio de entrada
Then el sistema segmenta el audio y asigna la transcripción a etiquetas como 'Speaker 1', 'Speaker 2'
And el analista puede renombrar esas etiquetas con los nombres reales de los participantes

Scenario: Audio con superposición de voces (Unhappy Path - Modelo)
Given una sesión activa donde dos personas hablan exactamente al mismo tiempo y fuerte
When el sistema intenta diarizar ese segmento superpuesto
Then el sistema agrupa ambas voces como 'Speaker X'
And advierte sutilmente en la transcripción sobre 'Superposición de voces detectada' | EP06 | -| **EP07** | **Procesamiento de Audio Grabado** | Funcionalidad para procesar grabaciones de reuniones anteriores cuando no fue posible usar la captura en tiempo real. | -- | -- | -| US39 | Subir grabación de reunión | Como miembro con permiso para crear sesiones, quiero subir el archivo de audio de una reunión pasada dentro de un proyecto específico, para que el sistema extraiga los requisitos aplicando el glosario y contexto técnico de ese proyecto. | Feature: Carga de grabaciones

Scenario: Procesar exitosamente una grabación válida (Happy Path)
Given que el usuario tiene permisos para crear sesiones
And la organización tiene minutos de procesamiento disponibles
When el usuario sube un archivo de audio válido
Then el sistema acepta el archivo
And inicia la extracción de requisitos automáticamente sin intervención manual

Scenario Outline: Rechazar carga de grabaciones inválidas o no permitidas (Unhappy Paths)
Given que el usuario intenta subir una grabación
When ocurre la situación descrita en <Condicion_Invalida>
Then el sistema rechaza la carga
And muestra el mensaje explicativo <Mensaje_Esperado>
And no descuenta minutos del plan de suscripción de la organización

Examples:
Condicion_InvalidaMensaje_Esperado
El archivo tiene un formato no soportado (ej. .pdf, .docx)El formato del archivo no está soportado.
El archivo de audio está corrupto o dañadoEl archivo de audio no se puede leer o está dañado.
El archivo supera el límite de tamaño máximo permitidoEl archivo supera el límite de tamaño permitido.
La organización agotó su límite de minutos mensualesHas alcanzado el límite de procesamiento de tu plan actual.
| EP07 | -| US12 | Manejo de límite de tokens (Chunking) en IA | Como sistema de procesamiento, quiero dividir los audios grandes en fragmentos manejables (Chunking) antes de enviarlos al LLM, para evitar errores de 'límite de tokens excedido' cuando las reuniones son muy largas. | Feature: Manejo de tokens y chunking

Scenario: Procesar audio extremadamente largo (Happy Path)
Given un archivo de audio que excede la ventana de contexto del LLM (ej. reunión de 2 horas)
When el sistema inicia el proceso de transcripción y análisis
Then divide internamente el audio en bloques lógicos (chunks) sin cortar oraciones por la mitad
And los procesa en lote para consolidar los requisitos sin error de límite de tokens

Scenario: Error al ensamblar chunks (Unhappy Path - Backend)
Given un audio dividido en 5 fragmentos
When el motor de IA procesa exitosamente 4 pero falla en 1 por intermitencia de red
Then el sistema reintenta exclusivamente el fragmento fallido en lugar de reiniciar todo el procesamiento
And garantiza que no se pierda la información ensamblando el resultado final de forma consistente | EP07 | -| US14 | Ver estado del procesamiento | Como miembro que subió un archivo de audio, quiero ver el avance del procesamiento de forma visible en la pantalla, para saber cuándo las historias están listas sin tener que revisar la sección manualmente cada cierto tiempo. Depende de: US30. | Feature: Estado del procesamiento

Scenario: Consultar el avance del procesamiento (Happy Path)
Given que un archivo de audio se subió exitosamente
When el usuario consulta la vista de procesamiento
Then el sistema muestra el estado actualizado (ej. 'En cola', 'Procesando', 'Completado')
And muestra un progreso estimado de ser posible

Scenario: Manejo de procesamiento fallido (Unhappy Path - Asíncrono)
Given que un archivo está en estado 'Procesando'
When el motor de IA falla o agota el tiempo de espera por un error interno
Then el estado de la tarea cambia a 'Fallido'
And la interfaz permite al usuario reintentar el procesamiento sin volver a subir el archivo | EP07 | -| **EP08** | **Asistencia Proactiva de la IA** | Capacidad del sistema para sugerir acciones al analista durante o después de la sesión con el fin de mejorar la calidad y completitud de los requisitos capturados. | -- | -- | -| US22 | Sugerencias de preguntas al cliente | Como analista durante o después de una sesión, quiero recibir una lista de preguntas concretas cuando el sistema detecta partes ambiguas en lo que dijo el cliente, para hacer esas preguntas antes de cerrar la reunión y evitar contactar al cliente después. Depende de: US26. | Feature: Sugerencias de preguntas al cliente

Scenario: Sugerir preguntas ante un requisito ambiguo (Happy Path)
Given que el sistema generó una historia a partir de la sesión
When la IA detecta partes ambiguas o contradictorias en lo dicho por el cliente
Then muestra una lista de preguntas concretas sugeridas
And vincula las preguntas al punto de ambigüedad específico

Scenario: Omitir sugerencias ante requisitos claros (Unhappy Path)
Given que el sistema generó una historia
When el requisito expresado por el cliente es completamente claro y detallado
Then el sistema no muestra sugerencias de preguntas irrelevantes | EP08 | -| US23 | Detección de casos no mencionados | Como analista revisando las historias generadas, quiero ver una lista de situaciones concretas que no se mencionaron en la reunión pero que suelen presentarse en ese tipo de módulo, para que el equipo las evalúe antes de empezar a construir y no las descubra durante el desarrollo. Depende de: US26. | Feature: Detección de casos no mencionados

Scenario: Proponer escenarios omitidos (Happy Path)
Given una historia de usuario estructurada sobre un módulo específico (ej. 'Login')
When el sistema analiza el contexto general de la historia
Then sugiere casos de uso típicos no mencionados por el cliente (ej. 'Bloqueo de cuenta tras N intentos fallidos')

Scenario Outline: Restricciones de sugerencia (Unhappy Paths)
Given una historia de usuario estructurada
When ocurre el escenario <Escenario>
Then el comportamiento del sistema será <Comportamiento>

Examples:
EscenarioComportamiento
El contexto del proyecto y el requisito son excesivamente genéricosNo inventa casos de uso sin fundamento ni genera ruido visual.
Existen docenas de posibles casos borde asociados al móduloFiltra la lista y muestra únicamente los N casos más críticos y probables.
| EP08 | -| US24 | Sugerencias de funcionalidades relacionadas | Como analista en una sesión activa, quiero recibir una lista de funcionalidades que otros sistemas del mismo sector suelen incluir y que el cliente no mencionó, para presentarlas como opciones concretas durante la reunión y que el cliente decida si las quiere o no. | Feature: Funcionalidades relacionadas

Scenario: Sugerir funcionalidades típicas del dominio (Happy Path)
Given un proyecto con un dominio de negocio bien definido (ej. E-commerce)
When el sistema procesa los requerimientos actuales
Then sugiere funcionalidades complementarias comunes en el mercado (ej. 'Recuperación de carrito')

Scenario: Omitir repeticiones durante la sesión (Edge Case)
Given una sesión larga en curso
When el sistema genera un nuevo lote de sugerencias relacionadas
Then filtra cualquier funcionalidad que ya haya sido sugerida o descartada previamente en la misma sesión | EP08 | -| **EP09** | **Calidad y Detección de Duplicados** | Búsqueda semántica sobre el historial de historias aprobadas del proyecto para mantener la integridad y unicidad del backlog. | -- | -- | -| US20 | Detección de historias similares | Como analista en una sesión activa, quiero que el sistema me avise con el porcentaje de similitud cuando una historia nueva se parece a una que ya existe en el proyecto, para evitar que el backlog tenga requisitos redundantes o contradictorios entre sí. | Feature: Detección de historias similares

Scenario: Alertar duplicidad evidente (Happy Path)
Given una nueva historia recién generada
When su significado excede el porcentaje de similitud configurado respecto a una historia previamente aprobada
Then el sistema muestra una alerta de posible duplicado
And enlaza a la historia original para comparación

Scenario: Ignorar historias relacionadas pero distintas (Unhappy Path)
Given una nueva historia recién generada
When comparte palabras clave con historias anteriores pero el objetivo funcional es diferente (no supera el umbral de similitud semántica)
Then el sistema no levanta ninguna alerta de duplicidad | EP09 | -| US21 | Resolver historia duplicada | Como analista que recibió una alerta de similitud, quiero decidir si fusiono la nueva historia con la existente o la mantengo separada dejando registrada mi justificación, para que el backlog quede limpio con una decisión explícita visible en el historial. Depende de: US35. | Feature: Resolver historia duplicada

Scenario: Resolver manteniendo la historia separada (Happy Path)
Given una alerta activa de historia similar
When el analista elige la opción 'Mantener separada'
And escribe una justificación válida de negocio
Then la alerta se da por resuelta
And la justificación queda documentada en el historial de la historia

Scenario Outline: Validaciones al intentar resolver (Unhappy Paths)
Given una alerta activa de historia similar
When el analista intenta resolverla con la falla <Falla>
Then el sistema bloquea la resolución
And exige corregir la entrada indicando <Requisito>

Examples:
FallaRequisito
No seleccionó ni 'Fusionar' ni 'Mantener separada'Debe elegir explícitamente una acción.
Eligió 'Mantener separada' pero dejó la justificación en blancoLa justificación es obligatoria para excepciones.

Scenario: Intento de resolución concurrente (Unhappy Path - Concurrencia)
Given una alerta de similitud visible para dos analistas simultáneamente
When el analista A resuelve la alerta y segundos después el analista B intenta hacer lo mismo
Then el sistema rechaza la acción del analista B
And notifica que la alerta ya fue resuelta, actualizando la vista | EP09 | -| **EP10** | **Revisión, Edición y Aprobación** | Fase final del flujo donde el analista valida y ajusta el trabajo de la IA antes de que las historias queden listas para el equipo de desarrollo. | -- | -- | -| US47 | Editar historia generada | Como miembro con permiso para editar historias, quiero modificar el texto de una historia generada por la IA, para ajustar el lenguaje al estándar que usa mi equipo antes de aprobarla. | Feature: Editar historia generada

Scenario: Guardar cambios en el contenido (Happy Path)
Given que un usuario con permisos se encuentra editando una historia generada por la IA
When modifica el texto y presiona guardar
Then la historia actualiza su contenido
And preserva la etiqueta o trazabilidad de que su origen inicial fue generado por IA

Scenario Outline: Validaciones del formulario de edición (Unhappy Paths)
Given que el usuario intenta guardar las modificaciones de una historia
When el formulario presenta <Error_Formulario>
Then el sistema bloquea el guardado
And muestra el mensaje <Mensaje_Error>

Examples:
Error_FormularioMensaje_Error
El título de la historia se ha borrado por completoEl título de la historia es obligatorio.
La descripción quedó completamente vacíaLa descripción no puede estar vacía.

Scenario: Conflicto de edición concurrente (Unhappy Path - Flujo Alternativo)
Given que dos usuarios abren la misma historia para editarla al mismo tiempo
When el usuario A guarda sus cambios y luego el usuario B intenta guardar los suyos
Then el sistema bloquea la acción del usuario B
And le advierte que la historia fue modificada externamente para evitar sobreescritura accidental | EP10 | -| US48 | Aprobar historia | Como miembro con permiso para aprobar historias, quiero marcar una historia como aprobada después de revisarla, para que quede disponible para el equipo de desarrollo y pueda exportarse al gestor de tareas. | Feature: Aprobar historia

Scenario: Aprobar historia revisada (Happy Path)
Given una historia completa en estado de revisión
When un usuario con permisos la marca como 'Aprobada'
Then el estado de la historia cambia a 'Aprobada'
And queda disponible en la cola para ser exportada a sistemas externos (ej. Jira)

Scenario Outline: Impedir aprobación de historias incompletas (Unhappy Paths)
Given que el usuario intenta aprobar una historia
When la historia tiene <Falla_Integridad>
Then la aprobación es rechazada
And el sistema indica <Razon_Rechazo>

Examples:
Falla_IntegridadRazon_Rechazo
Faltan definir los criterios de aceptaciónLa historia debe tener criterios de aceptación antes de ser aprobada.
La historia tiene alertas de similitud sin resolverDebes resolver los conflictos de duplicidad pendientes.
| EP10 | -| US17 | Resumen de cierre de sesión | Como analista al cerrar una sesión, quiero ver un resumen con el total de historias creadas, descartadas y pendientes de revisión, para confirmar con el cliente que los acuerdos quedaron correctamente registrados antes de dar la reunión por concluida. | Feature: Resumen de cierre de sesión

Scenario: Presentar totales precisos (Happy Path)
Given que una sesión de captura acaba de ser cerrada
When el sistema calcula el resumen final de la reunión
Then presenta una vista consolidada mostrando el total exacto de historias creadas, descartadas y pendientes de revisión

Scenario: Inconsistencia crítica durante cálculo (Unhappy Path - Sistema)
Given que una sesión está en proceso de cierre
When el backend detecta una discrepancia en el conteo de registros (inconsistencia de datos transaccional)
Then interrumpe el cierre de la sesión
And alerta al usuario sobre el problema solicitando actualizar la página antes de reintentar el cierre seguro | EP10 | -| US26 | Compartir historias con el cliente | Como analista, quiero generar un enlace público y seguro de solo lectura con las historias generadas, para enviarlo al cliente y que pueda validarlas o comentarlas sin necesidad de crearse una cuenta en la plataforma. | Feature: Aprobación externa del cliente

Scenario: Generar y visitar enlace público (Happy Path)
Given un proyecto con historias en revisión
When el analista genera un enlace público y el cliente accede a él
Then el cliente puede visualizar las historias en modo de solo lectura
And puede marcarlas como 'Aprobadas' o dejar comentarios

Scenario: Expiración o revocación del enlace (Unhappy Path)
Given un enlace público generado anteriormente
When el analista revoca el acceso o expira el tiempo de validez configurado
Then cualquier intento de ingresar mostrará un error de enlace no disponible | EP10 | -| US25 | Calificar calidad de historia (Feedback Loop) | Como analista revisando las historias generadas, quiero calificar la precisión de la IA (ej. Pulgar arriba/abajo) e indicar si hubo alucinaciones, para generar un registro de retroalimentación que mejore los prompts y el modelo en el futuro. | Feature: Retroalimentación de la IA (Feedback Loop)

Scenario: Calificar positivamente una historia (Happy Path)
Given una historia generada correctamente por la IA
When el analista la califica positivamente (ej. Pulgar arriba)
Then el sistema registra la métrica de éxito de forma silenciosa para el dataset de calidad

Scenario: Reportar alucinación o error de contexto (Unhappy Path - Mejora continua)
Given una historia que contiene datos inventados o malinterpretados por la IA
When el analista la califica negativamente (ej. Pulgar abajo)
Then el sistema despliega un menú opcional para categorizar el fallo (ej. 'Alucinación', 'Falta contexto')
And vincula el texto original generado con la versión final editada por el analista para afinar los modelos futuros | EP10 | -| **EP11** | **Integraciones Externas** | Conectividad con herramientas del ecosistema de desarrollo para transferir las historias aprobadas al backlog del equipo sin trabajo manual. | -- | -- | -| US27 | Conectar cuenta de Jira | Como miembro con permiso para configurar integraciones, quiero autorizar la conexión con Jira a través de un proceso seguro, para que la exportación de historias no requiera compartir credenciales con nadie del equipo. | Feature: Conectar cuenta de Jira

Scenario: Autorización segura y exitosa (Happy Path)
Given un usuario administrador en la sección de integraciones
When completa satisfactoriamente el flujo de autorización (OAuth) en la plataforma de Jira
Then el sistema de Reqs-AI vincula el proyecto a Jira
And muestra un indicador visual de conexión activa y saludable

Scenario Outline: Rechazo de autorización (Unhappy Paths)
Given que el usuario inicia el flujo de autorización
When ocurre el evento <Evento_Fallo>
Then el sistema aborta la integración
And muestra el estado <Estado_Final> sin guardar datos espurios

Examples:
Evento_FalloEstado_Final
El usuario deniega los permisos desde la ventana de JiraOperación cancelada por el usuario, integración inactiva.
Fallo de red o timeout durante la comunicación con JiraError de comunicación, inténtalo nuevamente.

Scenario: Token de conexión expirado (Unhappy Path - Flujo Alternativo)
Given una integración previamente configurada que estaba operativa
When el token de seguridad subyacente caduca (vencimiento de credencial)
Then el sistema deshabilita las opciones de exportación
And notifica explícitamente al administrador que se requiere una 'Re-autenticación' para reactivar la conexión | EP11 | -| US28 | Configurar mapeo de proyecto en Jira | Como administrador, quiero vincular un proyecto local de Reqs-AI con un Board/Proyecto específico de Jira, para que el sistema sepa exactamente a qué destino enviar las historias durante la exportación. | Feature: Mapeo de proyectos externos

Scenario: Vincular board destino (Happy Path)
Given una integración con Jira activa
When el usuario accede a la configuración del proyecto
Then puede seleccionar de una lista desplegable el 'Proyecto de Jira' y 'Tipo de Issue' destino
And el sistema guarda este mapeo para las futuras exportaciones

Scenario: Intentar exportar sin mapeo previo (Unhappy Path)
Given un proyecto sin board de destino configurado
When el analista intenta exportar historias a Jira
Then la exportación se bloquea
And el sistema redirige al usuario a la pantalla de mapeo de configuración | EP11 | -| US29 | Exportar historias a Jira | Como miembro con permiso para exportar historias, quiero enviar las historias aprobadas directamente al backlog de Jira, para que el equipo de desarrollo pueda planificar el sprint sin copiar ni pegar nada manualmente. Depende de: US38 y US41. | Feature: Exportar historias a Jira

Scenario: Exportación exitosa de historias aprobadas (Happy Path)
Given un proyecto con una conexión activa a Jira y varias historias en estado 'Aprobada'
When el usuario ejecuta la acción de exportación masiva
Then el sistema transmite únicamente las historias aprobadas a Jira
And marca exitosamente dichas historias como 'Exportadas' dentro de Reqs-AI

Scenario Outline: Validaciones de prerrequisitos de exportación (Unhappy Paths)
Given que el usuario intenta ejecutar la exportación masiva
When se incumple la condición <Incumplimiento>
Then el botón o acción es bloqueado o rechazado indicando <Mensaje_Error>

Examples:
IncumplimientoMensaje_Error
La integración con Jira no está configurada o el token expiróDebes conectar y autorizar tu cuenta de Jira primero.
No existe ninguna historia que se encuentre en estado 'Aprobada'No hay historias válidas listas para exportar.

Scenario: Fallo parcial durante la transmisión de lotes (Unhappy Path - Fallo de red)
Given un lote de 10 historias aprobadas listas para envío a la API de Jira
When la red experimenta intermitencia y 2 de las peticiones fallan
Then el sistema marca las 8 historias exitosas como 'Exportadas'
And retiene las 2 restantes, marcándolas con error de exportación y habilitando un botón para reintentar el lote fallido | EP11 | -| **EP12** | **Arquitectura y Servicios RESTful API** | Endpoints y servicios backend sin interfaz gráfica directa (Technical Stories), necesarios para soportar el procesamiento de IA, integraciones y lógica de negocio del frontend. | -- | -- | -| TS55 | API: Registro de Usuario (POST /auth/register) | Como Developer, quiero un endpoint para registrar usuarios hasheando su contraseña, para asegurar sus accesos. | Scenario: Registro exitoso
Given payload con email y password
When POST a /auth/register
Then 201 Created | EP12 | -| TS56 | API: Login de Usuario (POST /auth/login) | Como Developer, quiero un endpoint que valide credenciales y retorne un JWT. | Scenario: Login válido
Given credenciales correctas
When POST a /auth/login
Then 200 OK con token JWT | EP12 | -| TS57 | API: Perfil de Usuario (GET /auth/me) | Como Developer, quiero un endpoint que retorne los datos del usuario logueado según su JWT. | Scenario: Token válido
Given header Authorization: Bearer <token>
When GET a /auth/me
Then 200 OK | EP12 | -| TS58 | API: Crear Proyecto (POST /projects) | Como Developer, quiero un endpoint para crear un nuevo espacio de trabajo. | Scenario: Creación de proyecto
Given payload con nombre
When POST a /projects
Then 201 Created | EP12 | -| TS59 | API: Listar Proyectos (GET /projects) | Como Developer, quiero un endpoint para listar los proyectos del usuario autenticado. | Scenario: Listar proyectos
Given usuario con token válido
When GET a /projects
Then 200 OK con array de proyectos | EP12 | -| TS60 | API: Actualizar Proyecto (PUT /projects/{id}) | Como Developer, quiero un endpoint para modificar metadatos de un proyecto. | Scenario: Actualización válida
Given ID de proyecto existente
When PUT a /projects/{id}
Then 200 OK | EP12 | -| TS61 | API: Eliminar Proyecto (DELETE /projects/{id}) | Como Developer, quiero un endpoint de soft-delete para archivar proyectos. | Scenario: Eliminación lógica
Given ID válido
When DELETE a /projects/{id}
Then 204 No Content | EP12 | -| TS62 | API: Subir Audio (POST /sessions/upload) | Como Developer, quiero un endpoint que reciba un multipart/form-data y lo suba a Cloud Storage. | Scenario: Subida exitosa
Given archivo MP3
When POST a /sessions/upload
Then 200 OK con URL del storage | EP12 | -| TS63 | API: Transcribir Audio (POST /sessions/transcribe) | Como Developer, quiero un endpoint que envíe la URL del audio a Whisper/AWS y retorne texto. | Scenario: Transcripción exitosa
Given URL de audio válida
When POST a /sessions/transcribe
Then 200 OK con transcripción | EP12 | -| TS64 | API: Extraer Historias (POST /sessions/extract) | Como Developer, quiero un endpoint que procese la transcripción con el LLM y retorne el JSON de historias. | Scenario: Extracción LLM
Given texto transcrito
When POST a /sessions/extract
Then 200 OK con array JSON | EP12 | -| TS65 | API: Auth Jira (GET /integrations/jira/auth) | Como Developer, quiero un endpoint para iniciar el flujo OAuth2 con Atlassian. | Scenario: Redirección OAuth
Given petición de integración
When GET a /integrations/jira/auth
Then 302 Redirect a Jira | EP12 | -| TS66 | API: Exportar a Jira (POST /integrations/jira/export) | Como Developer, quiero un endpoint que mapee y envíe las historias al Webhook de Jira. | Scenario: Exportación a Webhook
Given array de IDs de historias
When POST a /integrations/jira/export
Then 200 OK confirmando creación | EP12 | +| ID | Título de la Historia | Descripción (Como... quiero... para...) | Criterios de Aceptación (BDD) | Épica Asociada | +|:---------|:-------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------| +| **EP00** | **Landing Page y Captación de Leads** | Landing page pública orientada a convertir visitantes en usuarios mediante la exposición de la propuesta de valor y beneficios por segmentos B2B. | -- | -- | +| US01 | Visualizar Propuesta de Valor (Hero) | Como visitante, quiero entender inmediatamente qué es Reqs-AI y su propuesta de valor principal en la cabecera (Hero), para decidir en los primeros segundos si me interesa continuar explorando la herramienta. | Feature: Landing Page - Hero Section

Scenario: Carga inicial y visibilidad de conversión (Happy Path)
Given un visitante que accede a la URL principal de Reqs-AI
When la página carga completamente
Then visualiza un título claro sobre la automatización de requisitos con IA
And un llamado a la acción (CTA) principal visible sin hacer scroll para iniciar el registro

Scenario: Acceso desde dispositivo móvil (Edge Case - Responsive)
Given un visitante que accede desde un dispositivo móvil (viewport inferior a 768px)
When la página carga completamente
Then el Hero adapta su diseño al ancho de pantalla sin perder legibilidad
And el CTA principal sigue visible y pulsable sin necesidad de hacer scroll

Scenario: Visitante ya autenticado (Edge Case)
Given un usuario que ya tiene sesión activa en la plataforma
When accede a la URL de la landing page
Then el sistema lo redirige directamente al panel principal de su organización
And no muestra la landing para evitar confusión innecesaria | EP00 | +| US02 | Explorar Casos de Uso por Segmento | Como visitante, quiero alternar entre diferentes perfiles (ej. Consultoras vs Startups) en una sección interactiva, para ver beneficios y ejemplos específicos que se adapten a la realidad de mi equipo. | Feature: Landing Page - Segmentación Interactiva

Scenario Outline: Alternar entre perfiles de usuario
Given un visitante en la sección de 'Construido para tu equipo'
When hace clic en la pestaña del perfil <Perfil>
Then el contenido dinámico se actualiza sin recargar la página
And muestra el beneficio principal <Beneficio> asociado a ese perfil

Examples:
PerfilBeneficio
Consultoras de SoftwareReducción de horas facturables en análisis y toma de requerimientos.
Product Managers / StartupsIntegración directa con Jira y adopción de metodologías ágiles.
| EP00 | +| US03 | Visualizar Planes y Precios | Como visitante evaluando la viabilidad financiera, quiero comparar los planes de suscripción (Gratuito, Pro, Equipo) de forma transparente, para determinar cuál se ajusta a mi presupuesto antes de crearme una cuenta. | Feature: Landing Page - Pricing

Scenario: Comparativa de planes con toggle (Happy Path)
Given un visitante en la sección de Precios
When alterna el interruptor entre facturación 'Mensual' y 'Anual'
Then los precios de los planes de pago se actualizan dinámicamente mostrando el descuento aplicado
And cada tarjeta de plan contiene un CTA que redirige al formulario de registro correspondiente

Scenario: CTA de plan gratuito no solicita datos de pago (Edge Case)
Given un visitante interesado en el Plan Gratuito
When hace clic en el CTA correspondiente
Then es redirigido al formulario de registro sin que se le solicite ningún dato de tarjeta ni método de pago

Scenario: Visualización sin interacción con el toggle (Edge Case)
Given un visitante que no interactúa con el toggle de facturación
When llega a la sección de precios
Then los planes se muestran en modo mensual por defecto
And cada tarjeta expone precio, límites principales y características clave de forma legible | EP00 | +| **EP01** | **Autenticación y Seguridad** | Gestión de acceso y protección de identidad para los usuarios de Reqs-AI, garantizando que solo personal autorizado acceda a la plataforma y su historial. | -- | -- | +| US04 | Registro de cuenta | Como visitante que llega por primera vez a la plataforma, quiero crear una cuenta con mi correo y una contraseña, para poder acceder a mis proyectos y sesiones de captura de requisitos. | Feature: Registro de cuenta

Scenario: Registro exitoso (Happy Path)
Given un visitante en la página de registro
When ingresa un correo válido y una contraseña segura
Then el sistema crea la cuenta en estado 'pendiente de verificación'
And envía un correo con el enlace de activación

Scenario Outline: Validaciones de registro (Unhappy Paths)
Given un visitante en la página de registro
When ingresa datos con el problema <Problema>
Then el sistema rechaza el registro indicando <Mensaje>

Examples:
ProblemaMensaje
El correo ya está registrado en otra cuentaEl correo ingresado ya se encuentra en uso.
La contraseña tiene menos de 8 caracteresLa contraseña debe tener al menos 8 caracteres.
El formato del correo es inválidoIngresa un correo electrónico válido.
| EP01 | +| US05 | Verificación de correo | Como usuario con cuenta pendiente de activación, quiero verificar mi correo haciendo clic en el enlace que recibí, para activar mi cuenta y empezar a trabajar en la plataforma. | Feature: Verificación de correo

Scenario: Verificación exitosa (Happy Path)
Given un usuario con cuenta pendiente de activación
When hace clic en el enlace de verificación recibido por correo
Then el sistema activa la cuenta
And redirige al usuario al inicio de sesión

Scenario: Enlace expirado o inválido (Unhappy Path)
Given un usuario con un enlace de verificación
When el enlace fue usado previamente o ya expiró su vigencia
Then el sistema muestra un error de enlace inválido
And ofrece la opción de reenviar un nuevo correo de verificación

Scenario: Reenvío de enlace de verificación (Flujo Alternativo)
Given un usuario cuyo enlace de verificación ha expirado
When solicita el reenvío del enlace desde la notificación de error
Then el sistema invalida el enlace anterior
And envía un nuevo enlace válido con un tiempo de expiración renovado | EP01 | +| US06 | Inicio de sesión | Como usuario con cuenta activa, quiero iniciar sesión con mi correo y contraseña, para acceder a mis proyectos y al historial de sesiones de mi organización. | Feature: Inicio de sesión

Scenario: Autenticación exitosa (Happy Path)
Given un usuario con cuenta activa
When ingresa sus credenciales correctas
Then el sistema le concede acceso
And lo redirige al panel principal de su última organización activa

Scenario Outline: Fallos de autenticación (Unhappy Paths)
Given un usuario intentando iniciar sesión
When ocurre la situación <Situacion>
Then el sistema deniega el acceso con el mensaje <Error>

Examples:
SituacionError
Contraseña incorrectaCredenciales inválidas.
La cuenta aún no ha sido verificadaDebes verificar tu correo antes de iniciar sesión.
Demasiados intentos fallidos consecutivosCuenta bloqueada temporalmente por seguridad.
| EP01 | +| US07 | Recuperación de contraseña | Como usuario registrado que no recuerda su contraseña, quiero recibir un enlace de restablecimiento en mi correo, para recuperar el acceso a mi cuenta sin perder mi historial de trabajo. | Feature: Recuperación de contraseña

Scenario: Solicitar recuperación (Happy Path)
Given un usuario que olvidó su contraseña
When ingresa su correo en el formulario de recuperación
Then el sistema envía un enlace de restablecimiento
And muestra un mensaje genérico de confirmación por seguridad

Scenario: Prevenir enumeración de usuarios (Edge Case - Seguridad)
Given un visitante malintencionado
When ingresa un correo que no existe en el sistema
Then el sistema no revela que el correo es inexistente
And muestra el mismo mensaje genérico de confirmación

Scenario: Enlace de restablecimiento expirado al usarlo (Unhappy Path)
Given un usuario que recibió el enlace de restablecimiento en su correo
When intenta acceder a él después de su período de validez (ej. pasadas 24 horas)
Then el sistema rechaza el token indicando que expiró
And ofrece la opción de solicitar un nuevo enlace de restablecimiento | EP01 | +| US08 | Cerrar sesión | Como usuario autenticado, quiero cerrar mi sesión de forma explícita, para asegurarme de que nadie más pueda acceder a mi cuenta desde el mismo dispositivo. | Feature: Cerrar sesión

Scenario: Logout exitoso (Happy Path)
Given un usuario autenticado
When selecciona la opción 'Cerrar sesión'
Then el sistema invalida el token de sesión
And redirige al usuario a la pantalla de inicio de sesión

Scenario: Cierre de sesión con captura activa (Edge Case)
Given un usuario con una sesión de captura en curso
When intenta cerrar sesión
Then el sistema muestra una advertencia de que hay una sesión activa
And requiere confirmar antes de proceder | EP01 | +| US09 | Aceptar términos y política de privacidad | Como nuevo usuario completando el registro, quiero leer y aceptar los términos de uso y la política de privacidad antes de activar mi cuenta, para entender cómo se usan mis datos y las grabaciones de audio antes de comenzar a usar el servicio. | Feature: Consentimiento de privacidad

Scenario: Aceptar términos durante el registro (Happy Path)
Given un visitante completando el formulario de registro
When marca la casilla de aceptación y envía el formulario
Then el sistema registra la fecha y versión del consentimiento otorgado
And procede con la creación de la cuenta

Scenario: Intentar registrarse sin aceptar términos (Unhappy Path)
Given un visitante en el formulario de registro
When intenta crear la cuenta sin marcar la casilla de aceptación
Then el sistema bloquea el envío
And resalta la casilla indicando que es obligatoria | EP01 | +| US10 | Editar perfil de usuario | Como usuario autenticado, quiero actualizar mi nombre y foto de perfil, para que mis compañeros de equipo me identifiquen correctamente en los registros de actividad de las sesiones. | Feature: Editar perfil

Scenario: Actualizar nombre y foto (Happy Path)
Given un usuario autenticado en su perfil
When modifica su nombre y sube una imagen válida
Then el sistema actualiza los datos
And refleja los cambios en todos los proyectos donde el usuario aparece

Scenario: Formato de imagen no soportado (Unhappy Path)
Given un usuario editando su perfil
When sube un archivo que no es imagen (ej. PDF)
Then el sistema rechaza el archivo
And muestra un mensaje indicando los formatos aceptados

Scenario: Imagen supera el tamaño máximo permitido (Unhappy Path)
Given un usuario editando su perfil
When intenta subir una imagen que excede el límite de tamaño (ej. mayor de 5 MB)
Then el sistema rechaza el archivo antes de procesarlo
And muestra el tamaño máximo permitido y los formatos válidos | EP01 | +| **EP02** | **Organizaciones** | Gestión de espacios de trabajo independientes para separar la información de diferentes empresas o equipos, garantizando que cada organización acceda únicamente a sus propios datos. | -- | -- | +| US11 | Crear organización | Como usuario autenticado que aún no pertenece a ninguna organización, quiero crear un espacio de trabajo con el nombre de mi empresa, para centralizar mis proyectos y gestionar mi equipo en un entorno separado. | Feature: Crear organización

Scenario: Creación exitosa (Happy Path)
Given un usuario autenticado sin organización
When crea una organización con un nombre válido
Then el sistema genera el espacio de trabajo
And asigna al usuario el rol inamovible de 'Propietario'

Scenario: Nombre de organización vacío (Unhappy Path)
Given un usuario creando una organización
When deja el nombre en blanco
Then el sistema bloquea la creación exigiendo un nombre obligatorio | EP02 | +| US12 | Editar organización | Como Propietario de la organización, quiero actualizar el nombre o los datos generales de mi organización, para mantener la información correcta cuando el equipo o el negocio cambia. | Feature: Editar organización

Scenario: Actualizar datos (Happy Path)
Given el Propietario de una organización
When modifica el nombre y guarda los cambios
Then la organización actualiza sus datos en toda la plataforma

Scenario: Intento de edición sin permisos (Unhappy Path)
Given un miembro regular de la organización
When intenta acceder a la configuración de la organización
Then el sistema oculta o bloquea la opción por falta de privilegios | EP02 | +| US13 | Cambiar de organización | Como usuario que pertenece a más de una organización, quiero seleccionar con cuál quiero trabajar desde el menú principal, para asegurarme de estar operando en el contexto correcto según el cliente que estoy atendiendo. | Feature: Cambiar de organización

Scenario: Cambio exitoso de contexto (Happy Path)
Given un usuario que pertenece a múltiples organizaciones
When selecciona una organización distinta desde el menú
Then el sistema recarga el contexto de trabajo
And muestra únicamente los proyectos de la organización seleccionada

Scenario: Eliminación concurrente (Edge Case)
Given un usuario intentando cambiar a la Organización B
When el propietario de la Organización B lo elimina en ese mismo instante
Then el sistema deniega el acceso y lo devuelve a su organización actual | EP02 | +| US14 | Política de retención de audios | Como Propietario de la organización, quiero configurar la eliminación automática de los archivos de audio originales X días después de procesarse, para cumplir con las políticas de privacidad y confidencialidad corporativas. | Feature: Política de retención de audios

Scenario: Eliminación automática al cumplir plazo (Happy Path)
Given una organización con la retención configurada en 7 días
When se cumple el plazo desde que un audio fue procesado
Then el sistema elimina permanentemente el archivo de audio físico
And conserva únicamente las historias generadas en texto

Scenario: Intentar acceder a un audio eliminado (Unhappy Path)
Given un audio que ya superó su periodo de retención y fue purgado
When un usuario intenta reproducirlo o descargarlo desde el historial
Then el sistema muestra un aviso de que el archivo fue eliminado por políticas de privacidad corporativa | EP02 | +| **EP03** | **Suscripción y Facturación** | Gestión de planes y límites de uso del sistema basados en un modelo de suscripción SaaS. Épica de baja prioridad — no entra en los primeros sprints. | -- | -- | +| US15 | Plan gratuito automático | Como usuario que acaba de crear su organización, quiero empezar a usar la plataforma sin ingresar datos de pago, para evaluar si se ajusta a mis necesidades antes de decidir suscribirme. | Feature: Plan gratuito automático

Scenario: Asignación por defecto (Happy Path)
Given un usuario que acaba de crear una nueva organización
When ingresa a su panel por primera vez
Then el sistema le asigna el 'Plan Gratuito'
And habilita las cuotas iniciales sin solicitar método de pago

Scenario: Cuota de sesiones del plan gratuito agotada (Edge Case)
Given una organización en Plan Gratuito que ya consumió su límite mensual de sesiones
When un analista intenta iniciar una nueva sesión de captura
Then el sistema bloquea la acción
And muestra un aviso invitando a actualizar al Plan Pro para continuar

Scenario: Intento de crear segunda organización en plan gratuito (Edge Case)
Given un usuario en Plan Gratuito con una organización activa
When intenta crear una segunda organización
Then el sistema bloquea la creación
And informa que el Plan Gratuito está limitado a un único espacio de trabajo | EP03 | +| US16 | Suscripción al plan Pro | Como Propietario de una organización en plan gratuito, quiero contratar el plan Pro, para acceder a sesiones ilimitadas, captura en tiempo real e integración con Jira. | Feature: Suscripción al plan Pro

Scenario: Upgrade de plan exitoso (Happy Path)
Given el Propietario de una organización en plan gratuito
When ingresa un método de pago válido y contrata el Plan Pro
Then los límites de la organización se expanden inmediatamente
And se emite la factura correspondiente

Scenario: Tarjeta rechazada (Unhappy Path)
Given un Propietario intentando hacer upgrade
When la pasarela de pagos rechaza la tarjeta por fondos insuficientes
Then el sistema mantiene el plan gratuito
And notifica el error de pago al usuario | EP03 | +| US17 | Suscripción al plan Equipo | Como Propietario de una organización en plan Pro, quiero contratar el plan Equipo, para poder agregar a todos los miembros de mi equipo y definir mediante roles personalizados qué puede hacer cada uno. | Feature: Suscripción al plan Equipo

Scenario: Upgrade a Equipo exitoso (Happy Path)
Given el Propietario de una organización en Plan Pro
When ingresa un método de pago válido y contrata el Plan Equipo
Then el plan se actualiza de inmediato
And se habilitan la gestión avanzada de roles y los asientos ilimitados de miembros
And se emite la factura correspondiente al nuevo plan

Scenario: Intento de upgrade sin plan Pro previo (Unhappy Path)
Given el Propietario de una organización en Plan Gratuito
When intenta contratar el Plan Equipo directamente
Then el sistema bloquea la operación
And lo redirige al flujo de upgrade al Plan Pro como paso previo requerido

Scenario: Pago rechazado durante upgrade (Unhappy Path)
Given el Propietario intentando contratar el Plan Equipo
When la pasarela de pagos rechaza el método de pago
Then el sistema mantiene el Plan Pro activo sin cambios
And notifica el error de pago solicitando actualizar el método de facturación | EP03 | +| US18 | Cancelación de suscripción | Como Propietario de una suscripción activa, quiero cancelar mi plan antes de que inicie el siguiente período de facturación, para no recibir cargos adicionales después de dejar de usar el servicio. | Feature: Cancelación de suscripción

Scenario: Cancelar antes de renovación (Happy Path)
Given el Propietario de una suscripción paga activa
When cancela la suscripción desde el panel
Then el sistema desactiva la auto-renovación
And mantiene los beneficios premium hasta el final del ciclo de facturación actual

Scenario: Reactivar suscripción antes del vencimiento (Flujo Alternativo)
Given una suscripción en estado CANCELING con días de acceso premium restantes
When el Propietario decide mantener el servicio
Then el sistema restaura la auto-renovación
And la suscripción vuelve al estado ACTIVE sin interrupciones ni cobros adicionales

Scenario: Intentar cancelar un plan gratuito (Edge Case)
Given el Propietario de una organización en Plan Gratuito
When accede al panel de cancelación
Then el sistema informa que el Plan Gratuito no genera cargos
And oculta la opción de cancelación para ese plan | EP03 | +| US19 | Ver estado del plan y consumo | Como Propietario de la organización, quiero ver el plan activo, la fecha de renovación y cuántas sesiones o proyectos he usado, para saber cuándo necesito actualizar mi plan antes de quedarme sin cupo. | Feature: Ver estado del plan y consumo

Scenario: Visualización de cuotas (Happy Path)
Given el Propietario de la organización
When ingresa a la sección de facturación
Then visualiza una barra de progreso con el consumo actual de sesiones frente al límite de su plan

Scenario: Límite alcanzado (Edge Case)
Given una organización que alcanzó exactamente el 100% de su límite
When el usuario visualiza el estado
Then el sistema muestra una alerta destacada invitando al upgrade

Scenario: Servicio de facturación no disponible (Unhappy Path - Sistema)
Given el Propietario accediendo a la sección de facturación
When el servicio de datos de suscripción falla temporalmente
Then el sistema muestra un mensaje de error con opción de reintento
And no expone datos parciales o incorrectos del plan al usuario | EP03 | +| **EP04** | **Gestión de Proyectos** | Creación, configuración y ciclo de vida de los proyectos por cliente, incluyendo la carga de conocimiento previo que permite a la IA generar historias más precisas. | -- | -- | +| US20 | Crear proyecto | Como analista, quiero registrar un proyecto asociado a un cliente específico, para organizar y separar todas las sesiones de levantamiento de requisitos de ese cliente. | Feature: Crear proyecto

Scenario: Creación de proyecto válida (Happy Path)
Given un analista con permisos para crear proyectos en la organización
When registra un nuevo proyecto indicando el nombre del cliente
Then el proyecto aparece en el listado activo de la organización

Scenario: Proyecto duplicado (Unhappy Path)
Given un usuario creando un proyecto
When ingresa un nombre que ya existe activo en la misma organización
Then el sistema sugiere usar un nombre distinto para evitar confusiones | EP04 | +| US21 | Cargar documentos del cliente | Como analista, quiero subir documentos del cliente (PDF, Word) al proyecto, para que el sistema extraiga y clasifique automáticamente su contenido en glosario, restricciones y contexto general, enriqueciendo así las historias que se generen en sesiones futuras. | Feature: Cargar documentos del cliente

Scenario: Carga y clasificación exitosa (Happy Path)
Given un usuario configurando un proyecto
When sube un documento PDF o Word válido
Then el sistema extrae el texto del documento
And clasifica el contenido en glosario, restricciones y contexto general
And lo incorpora a la base de conocimiento del proyecto para futuras sesiones

Scenario Outline: Restricciones de carga documental (Unhappy Paths)
Given un usuario intentando subir documentos de contexto
When ocurre el escenario <Fallo>
Then el sistema rechaza el archivo indicando <Error>

Examples:
FalloError
El archivo es un ejecutable (.exe)Solo se permiten documentos de texto o PDF.
El archivo excede los 50MBEl documento es demasiado pesado.
| EP04 | +| US22 | Configurar perfil técnico del proyecto | Como analista, quiero registrar las tecnologías que usa el equipo del cliente y los tipos de usuarios del sistema, para que los criterios de aceptación generados sean aplicables al contexto real del proyecto. | Feature: Configurar perfil técnico del proyecto

Scenario: Guardar perfil técnico (Happy Path)
Given un analista configurando el proyecto
When define que el stack es 'React + Node' y guarda
Then el sistema utiliza esta instrucción como prompt base para la generación de criterios técnicos en las historias de usuario

Scenario: Campo de stack vacío al guardar (Unhappy Path)
Given un analista en el formulario del perfil técnico
When intenta guardar sin haber definido ningún stack tecnológico
Then el sistema bloquea el guardado
And señala que el campo es obligatorio para la generación contextualizada de criterios

Scenario: Actualizar stack con historias ya generadas (Edge Case)
Given un proyecto que ya tiene historias generadas con un stack previo
When el analista actualiza el stack tecnológico
Then el sistema guarda la nueva configuración para futuras sesiones
And advierte que las historias existentes reflejan el stack anterior y no se actualizarán automáticamente | EP04 | +| US23 | Agregar término al glosario manualmente | Como analista, quiero agregar términos del negocio del cliente directamente al glosario del proyecto sin necesidad de subir un documento, para enriquecer el vocabulario de la IA cuando el cliente solo transmite su conocimiento de forma oral. | Feature: Glosario manual del proyecto

Scenario: Agregar término exitosamente (Happy Path)
Given un miembro configurando el glosario de un proyecto
When ingresa un término y su definición y los guarda
Then el término queda disponible para ser utilizado como contexto en futuras sesiones de captura

Scenario: Intentar guardar término sin definición (Unhappy Path)
Given un miembro en el formulario del glosario
When ingresa solo el término sin su definición
Then el sistema bloquea el guardado
And exige completar la definición antes de continuar

Scenario: Término duplicado en el glosario (Unhappy Path)
Given un miembro agregando términos al glosario del proyecto
When ingresa un término idéntico (ignorando mayúsculas) a uno ya registrado
Then el sistema detecta la duplicidad
And ofrece la opción de editar la definición del término existente en lugar de crear uno nuevo | EP04 | +| US24 | Registrar restricción técnica manualmente | Como analista, quiero registrar restricciones técnicas del cliente (ej. 'sin uso de cookies de terceros', 'máximo 3 segundos de latencia') directamente en el proyecto, para que la IA las considere al generar los criterios de aceptación sin necesidad de incluirlas en cada sesión. | Feature: Restricciones técnicas del proyecto

Scenario: Registrar restricción exitosamente (Happy Path)
Given un miembro configurando las restricciones del proyecto
When ingresa una restricción técnica y la guarda
Then la restricción queda registrada
And se incorpora al contexto de generación de historias en futuras sesiones

Scenario: Restricción duplicada (Unhappy Path)
Given un miembro agregando restricciones
When ingresa una restricción idéntica a una ya existente en el proyecto
Then el sistema alerta sobre la duplicidad
And ofrece la opción de editar la existente en lugar de crear una nueva | EP04 | +| US25 | Editar proyecto | Como analista, quiero modificar el nombre o la descripción de un proyecto existente, para corregir o actualizar la información cuando cambian los acuerdos con el cliente. | Feature: Editar proyecto

Scenario: Modificar datos del proyecto (Happy Path)
Given un analista con permisos de administración del proyecto
When cambia el nombre o la descripción del proyecto
Then los cambios se reflejan inmediatamente en el panel del equipo

Scenario: Nombre vacío al guardar (Unhappy Path)
Given un analista editando el nombre del proyecto
When borra el nombre por completo e intenta guardar
Then el sistema bloquea el guardado
And exige que el nombre del proyecto sea obligatorio

Scenario: Nombre duplicado en la organización (Unhappy Path)
Given un analista renombrando un proyecto
When ingresa un nombre igual al de otro proyecto activo en la misma organización
Then el sistema alerta sobre la colisión de nombres
And sugiere usar un sufijo o nombre alternativo para diferenciarlos | EP04 | +| US26 | Archivar proyecto | Como analista, quiero archivar un proyecto cuando termina el trabajo con ese cliente, para mantener el panel principal ordenado sin eliminar el historial de sesiones e historias generadas. | Feature: Archivar proyecto

Scenario: Archivar proyecto finalizado (Happy Path)
Given un proyecto activo
When el administrador lo archiva
Then el proyecto se oculta de las vistas principales pero mantiene su historial intacto

Scenario: Operaciones en proyecto archivado (Edge Case)
Given un proyecto en estado archivado
When un usuario intenta iniciar una nueva sesión de captura
Then el sistema bloquea la acción hasta que el proyecto sea desarchivado | EP04 | +| US27 | Proyecto de demostración automático | Como nuevo usuario que acaba de registrarse, quiero encontrar un proyecto de demostración pre-cargado con audios e historias ya generadas, para entender inmediatamente el valor de la plataforma sin tener que grabar mi propia reunión primero. | Feature: Proyecto Sandbox de Demo

Scenario: Cargar datos de demostración en nueva cuenta (Happy Path)
Given un usuario que acaba de registrarse exitosamente en la plataforma
When accede a su organización por primera vez
Then el sistema genera e inserta un proyecto de demostración pre-poblado (con audios, historias y transcripciones de ejemplo)
And lo marca visualmente como 'Sandbox / Demo' para que el usuario experimente de inmediato

Scenario: Restaurar proyecto de demostración (Flujo Alternativo)
Given un usuario que eliminó su proyecto de demostración
When hace clic en 'Restaurar datos de prueba' desde la configuración
Then el sistema vuelve a clonar el proyecto de ejemplo en su espacio de trabajo | EP04 | +| **EP05** | **Colaboración y Roles** | Gestión de roles personalizados con permisos configurables y administración de los miembros del equipo dentro de la organización y sus proyectos. El Propietario es el único rol fijo del sistema — es quien creó la organización, tiene todos los permisos y es el responsable del contrato. Todos los demás roles son creados y configurados por el Propietario. | -- | -- | +| US28 | Invitar miembro a la organización | Como Administrador de la organización, quiero invitar a un colega mediante su correo electrónico, para que pueda acceder a los proyectos que le correspondan dentro de la organización. | Feature: Invitar miembro

Scenario: Enviar invitación (Happy Path)
Given un usuario con permisos de gestión de equipo
When envía una invitación a un correo válido
Then el invitado recibe el correo con el enlace de acceso
And aparece en estado 'Pendiente' en el panel

Scenario Outline: Fallos de invitación (Unhappy Paths)
Given un administrador invitando a un usuario
When comete el error <Error_Inv>
Then la invitación falla indicando <Aviso>

Examples:
Error_InvAviso
El correo ya pertenece a la organizaciónEl usuario ya es miembro del equipo.
El correo ya tiene una invitación pendienteYa existe una invitación enviada a este correo.
| EP05 | +| US29 | Crear rol personalizado | Como Propietario de la organización, quiero crear un rol con un nombre definido por mi equipo, para representar una función real dentro de la organización en lugar de usar etiquetas genéricas del sistema. | Feature: Crear rol personalizado

Scenario: Rol creado exitosamente (Happy Path)
Given el Propietario de la organización
When crea el rol 'QA Lead' con un nombre único
Then el nuevo rol queda disponible para ser asignado a cualquier miembro

Scenario: Nombre de rol duplicado (Unhappy Path)
Given el Propietario creando un nuevo rol
When ingresa un nombre que ya existe entre los roles de la organización
Then el sistema bloquea la creación
And muestra que el nombre ya está en uso

Scenario: Nombre de rol vacío (Unhappy Path)
Given el Propietario en el formulario de creación de rol
When deja el campo de nombre vacío e intenta guardar
Then el sistema bloquea la acción
And señala que el nombre del rol es obligatorio | EP05 | +| US30 | Asignar permisos a un rol | Como Propietario de la organización, quiero seleccionar qué acciones puede realizar cada rol dentro de los proyectos y la organización, para que el nivel de acceso de cada miembro refleje exactamente sus responsabilidades reales. | Feature: Asignar permisos a un rol

Scenario: Configurar permisos (Happy Path)
Given el Propietario configurando un rol
When activa los permisos de 'Crear Sesiones' y 'Aprobar Historias'
Then todos los usuarios con ese rol adquieren dichas capacidades inmediatamente

Scenario: Revocar todos los permisos de un rol (Edge Case)
Given el Propietario editando los permisos de un rol existente
When intenta desactivar absolutamente todos los permisos disponibles
Then el sistema bloquea la acción
And advierte que un rol debe conservar al menos un permiso para ser funcional

Scenario: Cambio de permisos con miembros en sesión activa (Edge Case)
Given el Propietario revocando el permiso 'Crear Sesiones' de un rol que tienen analistas con sesiones abiertas
When confirma el cambio
Then el sistema aplica la restricción de inmediato
And notifica en tiempo real a los afectados que ya no pueden iniciar nuevas sesiones | EP05 | +| US31 | Asignar rol a un miembro | Como Administrador de la organización, quiero asignar uno de los roles disponibles a un miembro de la organización, para que sus permisos queden definidos en el momento en que acepta la invitación. | Feature: Asignar rol a un miembro

Scenario: Cambio de rol exitoso (Happy Path)
Given un Administrador de la organización
When cambia el rol del Usuario A de 'Lector' a 'Editor'
Then el Usuario A obtiene acceso a las herramientas de edición en su próxima navegación

Scenario: Intentar cambiar el rol del Propietario (Edge Case - Seguridad)
Given un Administrador de la organización
When intenta asignar un rol diferente al usuario Propietario de la cuenta
Then el sistema bloquea la acción
And recuerda que el rol de Propietario es fijo e intransferible

Scenario: Reasignar a rol con permisos reducidos (Unhappy Path - Confirmación)
Given un Administrador que va a reducir los permisos de un miembro activo
When cambia su rol a uno con menos privilegios
Then el sistema solicita confirmación antes de aplicar el cambio
And notifica al miembro afectado que sus permisos han sido actualizados | EP05 | +| US32 | Editar permisos de un rol existente | Como Propietario de la organización, quiero modificar los permisos de un rol ya creado, para ajustar el nivel de acceso de todos los miembros que lo tienen asignado cuando cambian sus responsabilidades. | Feature: Editar permisos de un rol existente

Scenario: Propagación de permisos (Happy Path)
Given un rol asignado a 5 usuarios
When el Propietario revoca el permiso de 'Exportar'
Then los 5 usuarios pierden la capacidad de exportar instantáneamente sin necesidad de re-login

Scenario: Editar rol sin miembros asignados (Edge Case)
Given un rol personalizado sin ningún miembro asignado actualmente
When el Propietario agrega permisos de 'Crear Sesiones' al rol
Then el sistema guarda los nuevos permisos sin ningún efecto inmediato
And los permisos se aplicarán cuando el rol sea asignado a un miembro

Scenario: Intentar dejar rol sin permisos (Unhappy Path)
Given el Propietario editando permisos de un rol
When revoca el último permiso activo del rol
Then el sistema bloquea la acción
And exige conservar al menos un permiso por integridad del rol | EP05 | +| US33 | Eliminar un rol | Como Propietario de la organización, quiero eliminar un rol que ya no corresponde a ninguna función activa del equipo, para mantener la lista de roles ordenada y evitar asignaciones incorrectas. | Feature: Eliminar un rol

Scenario: Eliminar rol sin uso (Happy Path)
Given un rol personalizado que no tiene usuarios asignados
When el Propietario lo elimina
Then el rol desaparece definitivamente de la organización

Scenario: Intento de eliminar rol en uso (Unhappy Path)
Given un rol que está asignado a al menos un miembro
When el Propietario intenta eliminarlo
Then el sistema bloquea la eliminación
And exige reasignar a esos miembros a otro rol antes de proceder | EP05 | +| US34 | Remover miembro de la organización | Como Administrador de la organización, quiero remover a un miembro de la organización, para revocar su acceso a todos los proyectos de forma inmediata cuando ya no forma parte del equipo. | Feature: Remover miembro de la organización

Scenario: Remoción exitosa (Happy Path)
Given un administrador de equipo
When remueve al Usuario B de la organización
Then el Usuario B pierde acceso inmediatamente a todos los proyectos de esa organización

Scenario: Intento de remover al Propietario (Edge Case - Seguridad)
Given un administrador de equipo
When intenta remover al Propietario original de la cuenta
Then el sistema bloquea la acción indicando que el rol de Propietario es intransferible e irremovible | EP05 | +| US35 | Transferir propiedad de la organización | Como Propietario de la organización, quiero transferir la propiedad a otro miembro activo, para poder desvincularme del equipo sin dejar la cuenta sin responsable cuando abandono el proyecto. | Feature: Transferencia de propiedad

Scenario: Transferir propiedad exitosamente (Happy Path)
Given el Propietario actual de la organización
When selecciona a un miembro activo y confirma la transferencia
Then el miembro seleccionado asume el rol de Propietario
And el Propietario anterior pasa a tener un rol regular configurable

Scenario: Intentar transferir a un miembro pendiente de invitación (Unhappy Path)
Given el Propietario intentando transferir la propiedad
When selecciona un usuario que aún no aceptó su invitación
Then el sistema bloquea la transferencia
And exige que el destinatario sea un miembro activo de la organización | EP05 | +| **EP06** | **Captura en Tiempo Real** | Funcionalidad principal del producto que procesa audio en vivo para generar historias de usuario de forma automática mientras la reunión con el cliente ocurre. Las sesiones requieren un proyecto existente. | -- | -- | +| US36 | Iniciar sesión de captura en vivo | Como analista, quiero activar la captura de audio durante la reunión dentro de un proyecto existente, para que el sistema identifique quién habla en cada momento y genere las historias a partir de lo que dice el cliente sin que yo tenga que tomar notas. Depende de: US20. | Feature: Captura en vivo

Scenario: Iniciar captura exitosamente (Happy Path)
Given que el analista tiene permisos y el proyecto está activo
When inicia la captura de audio en vivo
Then la sesión cambia a estado 'activa' y comienza a registrar el habla

Scenario Outline: Validaciones al intentar iniciar captura (Unhappy Paths)
Given que el analista intenta iniciar una sesión de captura en vivo
When ocurre la situación <Condicion>
Then el sistema impide el inicio de la sesión
And muestra el mensaje <Mensaje_Error>

Examples:
CondicionMensaje_Error
El navegador no tiene permisos de micrófonoDebes otorgar permisos de micrófono para continuar.
El usuario tiene un rol sin permisos de creaciónNo tienes permisos para crear sesiones en este proyecto.
El proyecto seleccionado se encuentra archivadoEl proyecto está archivado y no admite nuevas sesiones.

Scenario: Pérdida de conexión al iniciar (Unhappy Path - Flujo Alternativo)
Given que el analista inicia la captura
When se pierde la conexión a internet antes de confirmar con el servidor
Then el sistema cancela la creación de la sesión
And notifica al analista que verifique su conexión | EP06 | +| US37 | Generación automática de historias | Como analista en sesión activa, quiero que el sistema convierta automáticamente lo que dice el cliente en historias de usuario con criterios de aceptación, para obtener un backlog estructurado al finalizar la reunión sin necesidad de documentar después. | Feature: Generación automática de historias

Scenario: Extraer historia desde un requisito claro (Happy Path)
Given una sesión activa
When el sistema detecta un requisito válido en la intervención del cliente
Then genera una historia de usuario con criterios de aceptación
And la agrega al backlog de la sesión en tiempo real

Scenario: Manejo de audio incomprensible o ruido (Unhappy Path)
Given una sesión activa
When el cliente habla de temas irrelevantes o hay ruido de fondo
Then el sistema ignora la intervención
And no genera historias vacías ni interrumpe la captura

Scenario: Intervención larga con múltiples requisitos (Edge Case)
Given una sesión activa
When el cliente menciona varios requerimientos distintos en una sola intervención continua
Then el sistema separa lógicamente los temas
And crea una historia individual por cada requerimiento detectado | EP06 | +| US38 | Descartar historia durante sesión en vivo | Como analista con una sesión activa, quiero descartar una historia recién generada indicando un motivo, para mantener el backlog de la sesión limpio en tiempo real sin interrumpir la reunión. | Feature: Descarte de historia en vivo

Scenario: Descartar historia en sesión activa (Happy Path)
Given una historia recién generada en el backlog de la sesión activa
When el analista la descarta ingresando un motivo válido
Then la historia se oculta del backlog activo inmediatamente
And queda registrada en el historial de la sesión con su motivo

Scenario Outline: Validaciones al descartar (Unhappy Paths)
Given que el analista intenta descartar una historia
When el formulario presenta el problema <Problema>
Then el sistema bloquea el descarte
And solicita la corrección con el mensaje <Mensaje>

Examples:
ProblemaMensaje
El campo de motivo está completamente vacíoDebes ingresar un motivo para descartar la historia.
El motivo excede el límite de caracteres (ej. 500)El motivo es demasiado largo.


Scenario: Descartar historia ya descartada simultáneamente (Edge Case - Concurrencia)
Given dos analistas con la misma sesión activa abierta en paralelo
When el analista A descarta una historia y el analista B intenta descartar la misma instantes después
Then el sistema rechaza el intento del analista B
And refresca su vista indicando que la historia ya fue descartada | EP06 | +| US39 | Descartar historia en revisión post-sesión | Como analista revisando el backlog después de cerrar la sesión, quiero descartar una historia indicando un motivo, para depurar el resultado final antes de que el equipo de desarrollo lo utilice. | Feature: Descarte de historia en revisión

Scenario: Descartar historia en etapa de revisión (Happy Path)
Given una sesión cerrada con historias pendientes de revisión
When el analista descarta una historia ingresando un motivo válido
Then la historia pasa al estado 'Descartada'
And el motivo queda visible en el historial del proyecto

Scenario: Descarte concurrente (Edge Case)
Given una historia en revisión visible para dos analistas al mismo tiempo
When el analista A la descarta y segundos después el analista B intenta hacer lo mismo
Then el sistema informa al analista B que la historia ya fue descartada
And refresca su vista del backlog con el estado actualizado | EP10 | +| US40 | Consentimiento de grabación al iniciar sesión | Como analista que va a iniciar una sesión de captura, quiero que el sistema muestre un aviso claro indicando que se grabará audio antes de comenzar, para que todos los participantes sean informados y puedan dar su consentimiento. | Feature: Consentimiento de grabación

Scenario: Mostrar aviso y confirmar inicio (Happy Path)
Given un analista a punto de iniciar una sesión de captura
When hace clic en 'Iniciar sesión'
Then el sistema muestra un modal con el aviso de grabación de audio
And solo activa el micrófono después de que el analista confirma que los participantes fueron informados

Scenario: Cancelar inicio por falta de consentimiento (Unhappy Path)
Given el modal de consentimiento visible
When el analista cancela la acción
Then la sesión no se inicia
And el estado del proyecto permanece sin cambios | EP06 | +| US41 | Pausar y reanudar captura | Como analista durante una sesión activa, quiero pausar la captura cuando la conversación se desvía del tema o hay un receso, para evitar que se generen historias a partir de conversaciones que no son requisitos del cliente. | Feature: Pausa y reanudación

Scenario: Pausar y reanudar la captura (Happy Path)
Given una sesión en estado activa
When el analista pausa la sesión
Then el sistema detiene temporalmente la generación de historias
And al reanudarla, continúa el procesamiento normalmente

Scenario: Intentar pausar una sesión ya pausada (Unhappy Path de concurrencia)
Given una sesión que fue pausada por el analista A
When el analista B intenta pausarla desde otro dispositivo sin haber refrescado
Then el sistema ignora la acción
And sincroniza el estado de la UI para el analista B informando la pausa | EP06 | +| US42 | Cerrar y guardar sesión | Como analista al terminar una reunión, quiero finalizar la sesión de captura, para asegurar que todas las historias generadas queden guardadas en el historial del proyecto. | Feature: Cierre de sesión

Scenario: Cerrar sesión con historias guardadas (Happy Path)
Given una sesión activa con al menos una historia generada
When el analista cierra la sesión de captura
Then la sesión pasa a estado 'cerrada'
And todas las historias se persisten en el proyecto principal

Scenario: Prevenir pérdida de datos al cerrar (Unhappy Path - Flujo de Red)
Given una sesión activa con historias sin sincronizar al backend
When el usuario intenta cerrar la sesión pero no hay conexión a internet
Then el sistema bloquea el cierre temporalmente
And muestra una advertencia de sincronización pendiente para evitar pérdida de datos

Scenario: Cerrar sesión vacía (Edge Case)
Given una sesión activa en la que no se generó ninguna historia
When el analista cierra la sesión
Then el sistema permite el cierre
And muestra el resumen final con contadores en cero | EP06 | +| US43 | Abortar sesión sin guardar | Como analista en una sesión de captura, quiero poder abortar y descartar la reunión por completo si hubo un error (ej. reunión cancelada), para no ensuciar el historial del proyecto con datos basura. | Feature: Abortar sesión en vivo

Scenario: Cancelar y purgar sesión (Happy Path)
Given una sesión activa que no debe ser guardada
When el analista selecciona la opción 'Abortar y salir'
And confirma la advertencia destructiva
Then el sistema purga todas las historias generadas temporalmente
And cierra la sesión sin dejar rastro en el historial del proyecto

Scenario: Abortar por error sin confirmación (Unhappy Path)
Given una sesión activa
When el analista hace clic en 'Abortar'
Then el sistema muestra un modal de confirmación estricto pidiendo tipear 'ELIMINAR'
And no ejecuta el borrado hasta validar el input | EP06 | +| US44 | Etiquetar voz del cliente (Diarización) | Como analista revisando una sesión, quiero poder identificar y etiquetar qué voz corresponde al 'Cliente' y cuáles al 'Equipo', para que la IA priorice las necesidades del cliente y no genere historias basadas en nuestras propias preguntas. | Feature: Diarización (Etiquetado de locutor)

Scenario: Distinguir locutores automáticamente (Happy Path)
Given una sesión de grabación con múltiples participantes (ej. 2 clientes y 1 analista)
When el motor procesa el audio de entrada
Then el sistema segmenta el audio y asigna la transcripción a etiquetas como 'Speaker 1', 'Speaker 2'
And el analista puede renombrar esas etiquetas con los nombres reales de los participantes

Scenario: Audio con superposición de voces (Unhappy Path - Modelo)
Given una sesión activa donde dos personas hablan exactamente al mismo tiempo y fuerte
When el sistema intenta diarizar ese segmento superpuesto
Then el sistema agrupa ambas voces como 'Speaker X'
And advierte sutilmente en la transcripción sobre 'Superposición de voces detectada' | EP06 | +| **EP07** | **Procesamiento de Audio Grabado** | Funcionalidad para procesar grabaciones de reuniones anteriores cuando no fue posible usar la captura en tiempo real. | -- | -- | +| US45 | Subir grabación de reunión | Como analista, quiero subir el archivo de audio de una reunión pasada dentro de un proyecto específico, para que el sistema extraiga los requisitos aplicando el glosario y contexto técnico de ese proyecto. | Feature: Carga de grabaciones

Scenario: Procesar exitosamente una grabación válida (Happy Path)
Given que el analista tiene permisos para crear sesiones
And la organización tiene minutos de procesamiento disponibles
When el analista sube un archivo de audio válido
Then el sistema acepta el archivo
And inicia la extracción de requisitos automáticamente sin intervención manual

Scenario Outline: Rechazar carga de grabaciones inválidas o no permitidas (Unhappy Paths)
Given que el analista intenta subir una grabación
When ocurre la situación descrita en <Condicion_Invalida>
Then el sistema rechaza la carga
And muestra el mensaje explicativo <Mensaje_Esperado>
And no descuenta minutos del plan de suscripción de la organización

Examples:
Condicion_InvalidaMensaje_Esperado
El archivo tiene un formato no soportado (ej. .pdf, .docx)El formato del archivo no está soportado.
El archivo de audio está corrupto o dañadoEl archivo de audio no se puede leer o está dañado.
El archivo supera el límite de tamaño máximo permitidoEl archivo supera el límite de tamaño permitido.
La organización agotó su límite de minutos mensualesHas alcanzado el límite de procesamiento de tu plan actual.
| EP07 | +| US46 | Ver estado del procesamiento | Como analista que subió una grabación de reunión, quiero ver el avance del procesamiento de forma visible en la pantalla, para saber cuándo las historias están listas sin tener que revisar la sección manualmente cada cierto tiempo. Depende de: US45. | Feature: Estado del procesamiento

Scenario: Consultar el avance del procesamiento (Happy Path)
Given que el analista subió un archivo de audio exitosamente
When consulta la vista de procesamiento
Then el sistema muestra el estado actualizado (ej. 'En cola', 'Procesando', 'Completado')
And muestra un progreso estimado de ser posible

Scenario: Manejo de procesamiento fallido (Unhappy Path - Asíncrono)
Given que un archivo está en estado 'Procesando'
When el motor de IA falla o agota el tiempo de espera por un error interno
Then el estado de la tarea cambia a 'Fallido'
And la interfaz permite al analista reintentar el procesamiento sin volver a subir el archivo | EP07 | +| US47 | Ver historial de sesiones del proyecto | Como miembro del equipo, quiero ver el listado de todas las sesiones (en vivo y grabaciones) asociadas a un proyecto, para acceder rápidamente al resultado de reuniones anteriores sin tener que recordar fechas exactas. | Feature: Historial de sesiones

Scenario: Listar sesiones activas e históricas (Happy Path)
Given un miembro accediendo a la vista de un proyecto
When navega a la sección de historial de sesiones
Then el sistema muestra el listado de sesiones ordenadas por fecha descendente
And cada sesión indica su tipo (en vivo / grabación), fecha y número de historias generadas

Scenario: Proyecto sin sesiones previas (Edge Case)
Given un proyecto recién creado sin sesiones
When el miembro accede al historial
Then el sistema muestra un estado vacío con un mensaje orientativo

Scenario: Sesión con procesamiento de audio en curso (Edge Case)
Given un analista consultando el historial de sesiones del proyecto
When una grabación subida está todavía en proceso de transcripción y extracción
Then el sistema la muestra en el listado con el indicador de estado 'Procesando'
And desactiva las acciones de revisión hasta que el procesamiento finalice | EP07 | +| **EP08** | **Asistencia Proactiva de la IA** | Capacidad del sistema para sugerir acciones al analista durante o después de la sesión con el fin de mejorar la calidad y completitud de los requisitos capturados. | -- | -- | +| US48 | Sugerencias de preguntas al cliente | Como analista durante o después de una sesión, quiero recibir una lista de preguntas concretas cuando el sistema detecta partes ambiguas en lo que dijo el cliente, para hacer esas preguntas antes de cerrar la reunión y evitar contactar al cliente después. Depende de: US37. | Feature: Sugerencias de preguntas al cliente

Scenario: Sugerir preguntas ante un requisito ambiguo (Happy Path)
Given que el sistema generó una historia a partir de la sesión
When la IA detecta partes ambiguas o contradictorias en lo dicho por el cliente
Then muestra una lista de preguntas concretas sugeridas
And vincula las preguntas al punto de ambigüedad específico

Scenario: Omitir sugerencias ante requisitos claros (Unhappy Path)
Given que el sistema generó una historia
When el requisito expresado por el cliente es completamente claro y detallado
Then el sistema no muestra sugerencias de preguntas irrelevantes | EP08 | +| US49 | Detección de casos no mencionados | Como analista revisando las historias generadas, quiero ver una lista de situaciones concretas que no se mencionaron en la reunión pero que suelen presentarse en ese tipo de módulo, para que el equipo las evalúe antes de empezar a construir y no las descubra durante el desarrollo. Depende de: US37. | Feature: Detección de casos no mencionados

Scenario: Proponer escenarios omitidos (Happy Path)
Given una historia de usuario estructurada sobre un módulo específico (ej. 'Login')
When el sistema analiza el contexto general de la historia
Then sugiere casos de uso típicos no mencionados por el cliente (ej. 'Bloqueo de cuenta tras N intentos fallidos')

Scenario Outline: Restricciones de sugerencia (Unhappy Paths)
Given una historia de usuario estructurada
When ocurre el escenario <Escenario>
Then el comportamiento del sistema será <Comportamiento>

Examples:
EscenarioComportamiento
El contexto del proyecto y el requisito son excesivamente genéricosNo inventa casos de uso sin fundamento ni genera ruido visual.
Existen docenas de posibles casos borde asociados al móduloFiltra la lista y muestra únicamente los N casos más críticos y probables.
| EP08 | +| **EP09** | **Calidad y Detección de Duplicados** | Búsqueda semántica sobre el historial de historias aprobadas del proyecto para mantener la integridad y unicidad del backlog. | -- | -- | +| US50 | Detección de historias similares | Como analista en una sesión activa, quiero que el sistema me avise con el porcentaje de similitud cuando una historia nueva se parece a una que ya existe en el proyecto, para evitar que el backlog tenga requisitos redundantes o contradictorios entre sí. | Feature: Detección de historias similares

Scenario: Alertar duplicidad evidente (Happy Path)
Given una nueva historia recién generada
When su significado excede el porcentaje de similitud configurado respecto a una historia previamente aprobada
Then el sistema muestra una alerta de posible duplicado
And enlaza a la historia original para comparación

Scenario: Ignorar historias relacionadas pero distintas (Unhappy Path)
Given una nueva historia recién generada
When comparte palabras clave con historias anteriores pero el objetivo funcional es diferente (no supera el umbral de similitud semántica)
Then el sistema no levanta ninguna alerta de duplicidad | EP09 | +| US51 | Resolver historia duplicada | Como analista que recibió una alerta de similitud, quiero decidir si fusiono la nueva historia con la existente o la mantengo separada dejando registrada mi justificación, para que el backlog quede limpio con una decisión explícita visible en el historial. Depende de: US50. | Feature: Resolver historia duplicada

Scenario: Resolver manteniendo la historia separada (Happy Path)
Given una alerta activa de historia similar
When el analista elige la opción 'Mantener separada'
And escribe una justificación válida de negocio
Then la alerta se da por resuelta
And la justificación queda documentada en el historial de la historia

Scenario Outline: Validaciones al intentar resolver (Unhappy Paths)
Given una alerta activa de historia similar
When el analista intenta resolverla con la falla <Falla>
Then el sistema bloquea la resolución
And exige corregir la entrada indicando <Requisito>

Examples:
FallaRequisito
No seleccionó ni 'Fusionar' ni 'Mantener separada'Debe elegir explícitamente una acción.
Eligió 'Mantener separada' pero dejó la justificación en blancoLa justificación es obligatoria para excepciones.

Scenario: Intento de resolución concurrente (Unhappy Path - Concurrencia)
Given una alerta de similitud visible para dos analistas simultáneamente
When el analista A resuelve la alerta y segundos después el analista B intenta hacer lo mismo
Then el sistema rechaza la acción del analista B
And notifica que la alerta ya fue resuelta, actualizando la vista | EP09 | +| **EP10** | **Revisión, Edición y Aprobación** | Fase final del flujo donde el analista valida y ajusta el trabajo de la IA antes de que las historias queden listas para el equipo de desarrollo. | -- | -- | +| US52 | Editar historia generada | Como analista, quiero modificar el texto de una historia generada por la IA, para ajustar el lenguaje al estándar que usa mi equipo antes de aprobarla. | Feature: Editar historia generada

Scenario: Guardar cambios en el contenido (Happy Path)
Given que un analista se encuentra editando una historia generada por la IA
When modifica el texto y presiona guardar
Then la historia actualiza su contenido
And preserva la etiqueta o trazabilidad de que su origen inicial fue generado por IA

Scenario Outline: Validaciones del formulario de edición (Unhappy Paths)
Given que el analista intenta guardar las modificaciones de una historia
When el formulario presenta <Error_Formulario>
Then el sistema bloquea el guardado
And muestra el mensaje <Mensaje_Error>

Examples:
Error_FormularioMensaje_Error
El título de la historia se ha borrado por completoEl título de la historia es obligatorio.
La descripción quedó completamente vacíaLa descripción no puede estar vacía.

Scenario: Conflicto de edición concurrente (Unhappy Path - Flujo Alternativo)
Given que dos usuarios abren la misma historia para editarla al mismo tiempo
When el usuario A guarda sus cambios y luego el usuario B intenta guardar los suyos
Then el sistema bloquea la acción del usuario B
And le advierte que la historia fue modificada externamente para evitar sobreescritura accidental | EP10 | +| US53 | Aprobar historia | Como analista, quiero marcar una historia como aprobada después de revisarla, para que quede disponible para el equipo de desarrollo y pueda exportarse al gestor de tareas. | Feature: Aprobar historia

Scenario: Aprobar historia revisada (Happy Path)
Given una historia completa en estado de revisión
When un usuario con permisos la marca como 'Aprobada'
Then el estado de la historia cambia a 'Aprobada'
And queda disponible en la cola para ser exportada a sistemas externos (ej. Jira)

Scenario Outline: Impedir aprobación de historias incompletas (Unhappy Paths)
Given que el analista intenta aprobar una historia
When la historia tiene <Falla_Integridad>
Then la aprobación es rechazada
And el sistema indica <Razon_Rechazo>

Examples:
Falla_IntegridadRazon_Rechazo
Faltan definir los criterios de aceptaciónLa historia debe tener criterios de aceptación antes de ser aprobada.
La historia tiene alertas de similitud sin resolverDebes resolver los conflictos de duplicidad pendientes.
| EP10 | +| US54 | Resumen de cierre de sesión | Como analista al cerrar una sesión, quiero ver un resumen con el total de historias creadas, descartadas y pendientes de revisión, para confirmar con el cliente que los acuerdos quedaron correctamente registrados antes de dar la reunión por concluida. | Feature: Resumen de cierre de sesión

Scenario: Presentar totales precisos (Happy Path)
Given que una sesión de captura acaba de ser cerrada
When el sistema calcula el resumen final de la reunión
Then presenta una vista consolidada mostrando el total exacto de historias creadas, descartadas y pendientes de revisión

Scenario: Inconsistencia crítica durante cálculo (Unhappy Path - Sistema)
Given que una sesión está en proceso de cierre
When el backend detecta una discrepancia en el conteo de registros (inconsistencia de datos transaccional)
Then interrumpe el cierre de la sesión
And alerta al usuario sobre el problema solicitando actualizar la página antes de reintentar el cierre seguro | EP10 | +| US55 | Compartir historias con el cliente | Como analista, quiero generar un enlace público y seguro de solo lectura con las historias generadas, para enviarlo al cliente y que pueda validarlas o comentarlas sin necesidad de crearse una cuenta en la plataforma. | Feature: Aprobación externa del cliente

Scenario: Generar y visitar enlace público (Happy Path)
Given un proyecto con historias en revisión
When el analista genera un enlace público y el cliente accede a él
Then el cliente puede visualizar las historias en modo de solo lectura
And puede marcarlas como 'Aprobadas' o dejar comentarios

Scenario: Expiración o revocación del enlace (Unhappy Path)
Given un enlace público generado anteriormente
When el analista revoca el acceso o expira el tiempo de validez configurado
Then cualquier intento de ingresar mostrará un error de enlace no disponible

Scenario: Cliente externo sin cuenta accede al enlace (Edge Case - Usuario anónimo)
Given un cliente que no tiene cuenta en Reqs-AI y recibió el enlace público por correo
When accede a la URL del enlace desde su navegador
Then puede visualizar las historias en modo solo lectura sin necesidad de autenticarse
And no puede navegar a ninguna otra sección privada de la plataforma desde ese enlace | EP10 | +| US56 | Calificar calidad de historia (Feedback Loop) | Como analista revisando las historias generadas, quiero calificar la precisión de la IA (ej. Pulgar arriba/abajo) e indicar si hubo alucinaciones, para generar un registro de retroalimentación que mejore los prompts y el modelo en el futuro. | Feature: Retroalimentación de la IA (Feedback Loop)

Scenario: Calificar positivamente una historia (Happy Path)
Given una historia generada correctamente por la IA
When el analista la califica positivamente (ej. Pulgar arriba)
Then el sistema registra la métrica de éxito de forma silenciosa para el dataset de calidad

Scenario: Reportar alucinación o error de contexto (Unhappy Path - Mejora continua)
Given una historia que contiene datos inventados o malinterpretados por la IA
When el analista la califica negativamente (ej. Pulgar abajo)
Then el sistema despliega un menú opcional para categorizar el fallo (ej. 'Alucinación', 'Falta contexto')
And vincula el texto original generado con la versión final editada por el analista para afinar los modelos futuros | EP10 | +| US57 | Buscar y filtrar historias del backlog | Como analista revisando el backlog de un proyecto, quiero buscar historias por texto libre y filtrarlas por estado (generada, aprobada, descartada) o épica, para localizar rápidamente las historias que necesito revisar sin tener que desplazarme por todo el backlog. | Feature: Búsqueda y filtrado de historias

Scenario: Búsqueda por texto (Happy Path)
Given un analista en la vista del backlog de un proyecto con múltiples historias
When ingresa un término en el buscador
Then el sistema filtra y muestra únicamente las historias que contienen el término en título o descripción

Scenario: Filtrar por estado (Happy Path)
Given un analista en la vista del backlog
When selecciona el filtro 'Aprobadas'
Then el sistema muestra únicamente las historias en ese estado

Scenario: Sin resultados (Edge Case)
Given un analista aplicando un filtro muy específico
When ninguna historia coincide con los criterios
Then el sistema muestra un estado vacío con un mensaje claro | EP10 | +| **EP11** | **Integraciones Externas** | Conectividad con herramientas del ecosistema de desarrollo para transferir las historias aprobadas al backlog del equipo sin trabajo manual. | -- | -- | +| US58 | Conectar cuenta de Jira | Como Administrador de la organización, quiero autorizar la conexión con Jira a través de un proceso seguro, para que la exportación de historias no requiera compartir credenciales con nadie del equipo. | Feature: Conectar cuenta de Jira

Scenario: Autorización segura y exitosa (Happy Path)
Given un Administrador de la organización en la sección de integraciones
When completa satisfactoriamente el flujo de autorización (OAuth) en la plataforma de Jira
Then el sistema de Reqs-AI vincula el proyecto a Jira
And muestra un indicador visual de conexión activa y saludable

Scenario Outline: Rechazo de autorización (Unhappy Paths)
Given que el Administrador inicia el flujo de autorización
When ocurre el evento <Evento_Fallo>
Then el sistema aborta la integración
And muestra el estado <Estado_Final> sin guardar datos espurios

Examples:
Evento_FalloEstado_Final
El usuario deniega los permisos desde la ventana de JiraOperación cancelada por el usuario, integración inactiva.
Fallo de red o timeout durante la comunicación con JiraError de comunicación, inténtalo nuevamente.

Scenario: Token de conexión expirado (Unhappy Path - Flujo Alternativo)
Given una integración previamente configurada que estaba operativa
When el token de seguridad subyacente caduca (vencimiento de credencial)
Then el sistema deshabilita las opciones de exportación
And notifica explícitamente al administrador que se requiere una 'Re-autenticación' para reactivar la conexión | EP11 | +| US59 | Configurar mapeo de proyecto en Jira | Como Administrador de la organización, quiero vincular un proyecto local de Reqs-AI con un Board/Proyecto específico de Jira, para que el sistema sepa exactamente a qué destino enviar las historias durante la exportación. | Feature: Mapeo de proyectos externos

Scenario: Vincular board destino (Happy Path)
Given una integración con Jira activa
When el Administrador accede a la configuración de integración del proyecto
Then puede seleccionar de una lista desplegable el 'Proyecto de Jira' y 'Tipo de Issue' destino
And el sistema guarda este mapeo para las futuras exportaciones

Scenario: Intentar exportar sin mapeo previo (Unhappy Path)
Given un proyecto sin board de destino configurado
When el analista intenta exportar historias a Jira
Then la exportación se bloquea
And el sistema redirige al Administrador a la pantalla de configuración del mapeo

Scenario: Board de Jira eliminado externamente (Edge Case - Servicio externo)
Given un mapeo configurado a un board de Jira que fue eliminado desde Atlassian posteriormente
When el analista intenta ejecutar una exportación de historias
Then el sistema detecta que el destino ya no es accesible
And notifica al Administrador que el mapeo está roto y debe reconfigurarse antes de exportar | EP11 | +| US60 | Exportar historias a Jira | Como analista, quiero enviar las historias aprobadas directamente al backlog de Jira, para que el equipo de desarrollo pueda planificar el sprint sin copiar ni pegar nada manualmente. Depende de: US53 y US58. | Feature: Exportar historias a Jira

Scenario: Exportación exitosa de historias aprobadas (Happy Path)
Given un proyecto con una conexión activa a Jira y varias historias en estado 'Aprobada'
When el analista ejecuta la acción de exportación masiva
Then el sistema transmite únicamente las historias aprobadas a Jira
And marca exitosamente dichas historias como 'Exportadas' dentro de Reqs-AI

Scenario Outline: Validaciones de prerrequisitos de exportación (Unhappy Paths)
Given que el analista intenta ejecutar la exportación masiva
When se incumple la condición <Incumplimiento>
Then el botón o acción es bloqueado o rechazado indicando <Mensaje_Error>

Examples:
IncumplimientoMensaje_Error
La integración con Jira no está configurada o el token expiróDebes conectar y autorizar tu cuenta de Jira primero.
No existe ninguna historia que se encuentre en estado 'Aprobada'No hay historias válidas listas para exportar.

Scenario: Fallo parcial durante la transmisión de lotes (Unhappy Path - Fallo de red)
Given un lote de 10 historias aprobadas listas para envío a la API de Jira
When la red experimenta intermitencia y 2 de las peticiones fallan
Then el sistema marca las 8 historias exitosas como 'Exportadas'
And retiene las 2 restantes, marcándolas con error de exportación y habilitando un botón para reintentar el lote fallido | EP11 | +| **EP12** | **Arquitectura y Servicios RESTful API** | Endpoints y servicios backend sin interfaz gráfica directa (Technical Stories), necesarios para soportar el procesamiento de IA, integraciones y lógica de negocio del frontend. | -- | -- | +| TS01 | API: Registro de Usuario (POST /auth/register) | Como Developer, quiero un endpoint para registrar usuarios hasheando su contraseña, para asegurar sus accesos. | Scenario: Registro exitoso
Given payload con email y password
When POST a /auth/register
Then 201 Created | EP12 | +| TS02 | API: Login de Usuario (POST /auth/login) | Como Developer, quiero un endpoint que valide credenciales y retorne un JWT. | Scenario: Login válido
Given credenciales correctas
When POST a /auth/login
Then 200 OK con token JWT | EP12 | +| TS03 | API: Perfil de Usuario (GET /auth/me) | Como Developer, quiero un endpoint que retorne los datos del usuario logueado según su JWT. | Scenario: Token válido
Given header Authorization: Bearer <token>
When GET a /auth/me
Then 200 OK | EP12 | +| TS04 | API: Crear Proyecto (POST /projects) | Como Developer, quiero un endpoint para crear un nuevo espacio de trabajo. | Scenario: Creación de proyecto
Given payload con nombre
When POST a /projects
Then 201 Created | EP12 | +| TS05 | API: Listar Proyectos (GET /projects) | Como Developer, quiero un endpoint para listar los proyectos del usuario autenticado. | Scenario: Listar proyectos
Given usuario con token válido
When GET a /projects
Then 200 OK con array de proyectos | EP12 | +| TS06 | API: Actualizar Proyecto (PUT /projects/{id}) | Como Developer, quiero un endpoint para modificar metadatos de un proyecto. | Scenario: Actualización válida
Given ID de proyecto existente
When PUT a /projects/{id}
Then 200 OK | EP12 | +| TS07 | API: Eliminar Proyecto (DELETE /projects/{id}) | Como Developer, quiero un endpoint de soft-delete para archivar proyectos. | Scenario: Eliminación lógica
Given ID válido
When DELETE a /projects/{id}
Then 204 No Content | EP12 | +| TS08 | API: Subir Audio (POST /sessions/upload) | Como Developer, quiero un endpoint que reciba un multipart/form-data y lo suba a Cloud Storage. | Scenario: Subida exitosa
Given archivo MP3
When POST a /sessions/upload
Then 200 OK con URL del storage | EP12 | +| TS09 | API: Transcribir Audio (POST /sessions/transcribe) | Como Developer, quiero un endpoint que envíe la URL del audio a Whisper/AWS y retorne texto. | Scenario: Transcripción exitosa
Given URL de audio válida
When POST a /sessions/transcribe
Then 200 OK con transcripción | EP12 | +| TS10 | API: Extraer Historias (POST /sessions/extract) | Como Developer, quiero un endpoint que procese la transcripción con el LLM y retorne el JSON de historias. | Scenario: Extracción LLM
Given texto transcrito
When POST a /sessions/extract
Then 200 OK con array JSON | EP12 | +| TS11 | API: Auth Jira (GET /integrations/jira/auth) | Como Developer, quiero un endpoint para iniciar el flujo OAuth2 con Atlassian. | Scenario: Redirección OAuth
Given petición de integración
When GET a /integrations/jira/auth
Then 302 Redirect a Jira | EP12 | +| TS12 | API: Exportar a Jira (POST /integrations/jira/export) | Como Developer, quiero un endpoint que mapee y envíe las historias al Webhook de Jira. | Scenario: Exportación a Webhook
Given array de IDs de historias
When POST a /integrations/jira/export
Then 200 OK confirmando creación | EP12 | +| TS13 | Servicio interno: Chunking de audio para ventana de tokens del LLM | Como Developer, quiero que el pipeline de procesamiento divida automáticamente los audios largos en fragmentos semánticos manejables antes de enviarlos al LLM, para evitar errores de límite de tokens y garantizar coherencia en reuniones extensas. | Scenario: Chunking de audio largo
Given audio que supera la ventana de contexto del LLM
When el pipeline de procesamiento lo recibe
Then lo divide en bloques lógicos sin cortar oraciones
And los consolida al finalizar sin pérdida de información

Scenario: Reintento de fragmento fallido
Given un audio dividido en N chunks
When un chunk falla por intermitencia de red
Then el sistema reintenta únicamente ese fragmento sin relanzar todo el proceso | EP12 | +| TS14 | API: Cerrar Sesión de Usuario (POST /auth/logout) | Como Developer, quiero un endpoint que invalide el token JWT activo del usuario. | Scenario: Logout exitoso
Given token JWT válido en header
When POST a /auth/logout
Then 200 OK y token invalidado en blacklist | EP12 | +| TS15 | API: Verificar Email (POST /auth/verify-email) | Como Developer, quiero un endpoint que valide el token de verificación enviado por correo. | Scenario: Verificación válida
Given token de verificación vigente
When POST a /auth/verify-email
Then 200 OK y cuenta marcada como verificada | EP12 | +| TS16 | API: Recuperar Contraseña (POST /auth/forgot-password) | Como Developer, quiero un endpoint que genere y envíe un enlace de reset de contraseña al email. | Scenario: Solicitud de reset
Given email registrado en el sistema
When POST a /auth/forgot-password
Then 200 OK y email de reset enviado | EP12 | +| TS17 | API: Actualizar Perfil de Usuario (PUT /auth/profile) | Como Developer, quiero un endpoint que permita al usuario autenticado editar nombre, avatar y preferencias. | Scenario: Actualización válida
Given usuario autenticado con token JWT
When PUT a /auth/profile con body válido
Then 200 OK con perfil actualizado | EP12 | +| TS18 | API: Crear Organización (POST /organizations) | Como Developer, quiero un endpoint que cree una nueva organización y asigne al creador como Owner. | Scenario: Creación exitosa
Given usuario autenticado sin organización activa
When POST a /organizations con nombre válido
Then 201 Created con ID de organización y rol Owner asignado | EP12 | +| TS19 | API: Actualizar Organización (PUT /organizations/{id}) | Como Developer, quiero un endpoint para modificar nombre, logo y configuraciones de la organización. | Scenario: Actualización válida
Given usuario con permiso de Owner en la organización
When PUT a /organizations/{id} con datos válidos
Then 200 OK con organización actualizada | EP12 | +| TS20 | API: Cambiar Contexto de Organización (POST /organizations/switch) | Como Developer, quiero un endpoint que cambie la organización activa en la sesión del usuario. | Scenario: Cambio exitoso
Given usuario miembro de múltiples organizaciones
When POST a /organizations/switch con el ID destino
Then 200 OK con nuevo JWT que refleja el contexto actualizado | EP12 | +| TS21 | API: Invitar Miembro (POST /organizations/{id}/invitations) | Como Developer, quiero un endpoint que genere y envíe una invitación por email para unirse a la organización. | Scenario: Invitación enviada
Given usuario Owner o Admin con permisos de invitación
When POST a /organizations/{id}/invitations con email y rol
Then 201 Created y email de invitación enviado | EP12 | +| TS22 | API: Crear Rol Personalizado (POST /organizations/{id}/roles) | Como Developer, quiero un endpoint que registre un nuevo rol con nombre y conjunto de permisos. | Scenario: Creación de rol
Given usuario Owner de la organización
When POST a /organizations/{id}/roles con nombre y permisos
Then 201 Created con ID del nuevo rol | EP12 | +| TS23 | API: Configurar Permisos de Rol (PUT /organizations/{id}/roles/{roleId}) | Como Developer, quiero un endpoint que actualice los permisos asociados a un rol existente. | Scenario: Actualización de permisos
Given rol existente en la organización
When PUT a /organizations/{id}/roles/{roleId} con nuevos permisos
Then 200 OK con rol actualizado | EP12 | +| TS24 | API: Eliminar Rol (DELETE /organizations/{id}/roles/{roleId}) | Como Developer, quiero un endpoint que elimine un rol personalizado si no tiene miembros asignados. | Scenario: Eliminación válida
Given rol sin miembros asignados
When DELETE a /organizations/{id}/roles/{roleId}
Then 204 No Content
Scenario: Rol en uso (Unhappy Path)
Given rol con miembros activos
When DELETE a /organizations/{id}/roles/{roleId}
Then 409 Conflict con mensaje indicando miembros activos | EP12 | +| TS25 | API: Crear Sesión en Vivo (POST /sessions) | Como Developer, quiero un endpoint que inicialice una sesión de captura en vivo asociada a un proyecto. | Scenario: Creación exitosa
Given proyecto activo y usuario con permiso de edición
When POST a /sessions con ID de proyecto
Then 201 Created con ID de sesión y estado OPEN | EP12 | +| TS26 | API: Cambiar Estado de Sesión (PATCH /sessions/{id}/status) | Como Developer, quiero un endpoint que cambie el estado de una sesión entre OPEN, PAUSED y CLOSED. | Scenario: Cierre de sesión
Given sesión en estado OPEN
When PATCH a /sessions/{id}/status con estado CLOSED
Then 200 OK y sesión marcada como CLOSED | EP12 | +| TS27 | API: Listar Sesiones de Proyecto (GET /projects/{id}/sessions) | Como Developer, quiero un endpoint paginado que retorne todas las sesiones de un proyecto ordenadas por fecha. | Scenario: Listado exitoso
Given proyecto con sesiones registradas
When GET a /projects/{id}/sessions
Then 200 OK con array paginado de sesiones | EP12 | +| TS28 | API: Listar y Buscar Historias (GET /projects/{id}/stories) | Como Developer, quiero un endpoint con filtros por estado, épica y texto para recuperar historias de un proyecto. | Scenario: Búsqueda con filtro
Given proyecto con historias generadas
When GET a /projects/{id}/stories?status=DRAFT&epic=EP03
Then 200 OK con array filtrado y metadata de paginación | EP12 | +| TS29 | API: Editar Historia (PUT /stories/{id}) | Como Developer, quiero un endpoint que permita actualizar título, descripción y criterios de aceptación de una historia. | Scenario: Edición válida
Given historia existente y usuario con permiso de edición
When PUT a /stories/{id} con body actualizado
Then 200 OK con historia actualizada y timestamp de modificación | EP12 | +| TS30 | API: Cambiar Estado de Historia (PATCH /stories/{id}/status) | Como Developer, quiero un endpoint que cambie el estado de una historia entre DRAFT, APPROVED y REJECTED. | Scenario: Aprobación de historia
Given historia en estado DRAFT
When PATCH a /stories/{id}/status con estado APPROVED
Then 200 OK y historia marcada como APPROVED | EP12 | +| TS31 | API: Cargar Documento (POST /projects/{id}/documents) | Como Developer, quiero un endpoint multipart que acepte PDF/Word, extraiga el texto con Apache Tika y genere embeddings con el LLM. | Scenario: Carga y procesamiento
Given archivo PDF válido
When POST a /projects/{id}/documents
Then 202 Accepted y procesamiento asíncrono iniciado; webhook notifica al completar | EP12 | +| TS32 | API: Agregar Término al Glosario (POST /projects/{id}/glossary) | Como Developer, quiero un endpoint que persista un término con su definición en el contexto de un proyecto. | Scenario: Término agregado
Given proyecto activo y usuario autenticado
When POST a /projects/{id}/glossary con término y definición
Then 201 Created con ID del término | EP12 | +| TS33 | API: Agregar Restricción Técnica (POST /projects/{id}/constraints) | Como Developer, quiero un endpoint que registre una restricción técnica asociada al proyecto para enriquecer el contexto del RAG. | Scenario: Restricción registrada
Given proyecto activo y usuario autenticado
When POST a /projects/{id}/constraints con descripción
Then 201 Created con ID de la restricción | EP12 | +| TS34 | API: Upgrade de Plan (POST /subscriptions/upgrade) | Como Developer, quiero un endpoint que inicie el flujo de pago con Stripe y actualice el plan al confirmar el webhook. | Scenario: Upgrade exitoso
Given organización en plan FREE
When POST a /subscriptions/upgrade con plan destino
Then 200 OK con URL de checkout de Stripe | EP12 | +| TS35 | API: Cancelar Suscripción (DELETE /subscriptions) | Como Developer, quiero un endpoint que marque la suscripción como cancelada al final del período de facturación. | Scenario: Cancelación programada
Given organización con suscripción activa
When DELETE a /subscriptions
Then 200 OK con fecha de expiración y estado CANCELING | EP12 | ## 3.3. Product Backlog @@ -954,68 +1019,103 @@ Esta priorización se rige por el **Valor de Negocio**: las funcionalidades que **Tabla de Estimación y Priorización** -| # Orden | User Story Id | Título | Descripción | Story Points (1/2/3/5/8) | -|:-------:|:--------------|:------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------:| -| **1** | **US01** | Visualizar Propuesta de Valor (Hero) | Como visitante, quiero entender inmediatamente qué es Reqs-AI y su propuesta de valor principal en la cabecera (Hero), para decidir en los primeros segundos si me interesa continuar explorando la herramienta. | **3** | -| **2** | **US02** | Explorar Casos de Uso por Segmento | Como visitante, quiero alternar entre diferentes perfiles (ej. Consultoras vs Startups) en una sección interactiva, para ver beneficios y ejemplos específicos que se adapten a la realidad de mi equipo. | **3** | -| **3** | **US03** | Validar Credibilidad (Social Proof) | Como visitante escéptico, quiero ver testimonios, métricas de éxito o logos de empresas que ya usan la herramienta, para sentir confianza antes de invertir mi tiempo o dinero en la plataforma. | **2** | -| **4** | **US04** | Visualizar Planes y Precios | Como visitante evaluando la viabilidad financiera, quiero comparar los planes de suscripción (Gratuito, Pro, Equipo) de forma transparente, para determinar cuál se ajusta a mi presupuesto antes de crearme una cuenta. | **3** | -| **5** | **US35** | Pausar y reanudar captura | Como analista durante una sesión activa, quiero pausar la captura cuando la conversación se desvía del tema o hay un receso, para evitar que se generen historias a partir de conversaciones que no son requisitos del cliente. | **2** | -| **6** | **US36** | Cerrar y guardar sesión | Como analista al terminar una reunión, quiero finalizar la sesión de captura, para asegurar que todas las historias generadas queden guardadas en el historial del proyecto. | **2** | -| **7** | **US34** | Descartar historia (Componente Unificado) | Como analista, quiero descartar una historia generada indicando un motivo, tanto durante la sesión en vivo como en la etapa de revisión posterior, utilizando una misma interfaz para mantener el backlog limpio. | **3** | -| **8** | **US37** | Abortar sesión sin guardar | Como analista en una sesión de captura, quiero poder abortar y descartar la reunión por completo si hubo un error (ej. reunión cancelada), para no ensuciar el historial del proyecto con datos basura. | **2** | -| **9** | **TS62** | API: Subir Audio (POST /sessions/upload) | Como Developer, quiero un endpoint que reciba un multipart/form-data y lo suba a Cloud Storage. | **3** | -| **10** | **TS63** | API: Transcribir Audio (POST /sessions/transcribe) | Como Developer, quiero un endpoint que envíe la URL del audio a Whisper/AWS y retorne texto. | **5** | -| **11** | **TS64** | API: Extraer Historias (POST /sessions/extract) | Como Developer, quiero un endpoint que procese la transcripción con el LLM y retorne el JSON de historias. | **5** | -| **12** | **US38** | Etiquetar voz del cliente (Diarización) | Como analista revisando una sesión, quiero poder identificar y etiquetar qué voz corresponde al 'Cliente' y cuáles al 'Equipo', para que la IA priorice las necesidades del cliente y no genere historias basadas en nuestras propias preguntas. | **5** | -| **13** | **US40** | Manejo de límite de tokens (Chunking) en IA | Como sistema de procesamiento, quiero dividir los audios grandes en fragmentos manejables (Chunking) antes de enviarlos al LLM, para evitar errores de 'límite de tokens excedido' cuando las reuniones son muy largas. | **8** | -| **14** | **US41** | Ver estado del procesamiento | Como miembro que subió un archivo de audio, quiero ver el avance del procesamiento de forma visible en la pantalla, para saber cuándo las historias están listas sin tener que revisar la sección manualmente cada cierto tiempo. Depende de: US30. | **2** | -| **15** | **US45** | Detección de historias similares | Como analista en una sesión activa, quiero que el sistema me avise con el porcentaje de similitud cuando una historia nueva se parece a una que ya existe en el proyecto, para evitar que el backlog tenga requisitos redundantes o contradictorios entre sí. | **5** | -| **16** | **US46** | Resolver historia duplicada | Como analista que recibió una alerta de similitud, quiero decidir si fusiono la nueva historia con la existente o la mantengo separada dejando registrada mi justificación, para que el backlog quede limpio con una decisión explícita visible en el historial. Depende de: US35. | **3** | -| **17** | **US42** | Sugerencias de preguntas al cliente | Como analista durante o después de una sesión, quiero recibir una lista de preguntas concretas cuando el sistema detecta partes ambiguas en lo que dijo el cliente, para hacer esas preguntas antes de cerrar la reunión y evitar contactar al cliente después. Depende de: US26. | **5** | -| **18** | **US43** | Detección de casos no mencionados | Como analista revisando las historias generadas, quiero ver una lista de situaciones concretas que no se mencionaron en la reunión pero que suelen presentarse en ese tipo de módulo, para que el equipo las evalúe antes de empezar a construir y no las descubra durante el desarrollo. Depende de: US26. | **3** | -| **19** | **US44** | Sugerencias de funcionalidades relacionadas | Como analista en una sesión activa, quiero recibir una lista de funcionalidades que otros sistemas del mismo sector suelen incluir y que el cliente no mencionó, para presentarlas como opciones concretas durante la reunión y que el cliente decida si las quiere o no. | **3** | -| **20** | **US21** | Configurar perfil técnico del proyecto | Como miembro con permiso para configurar proyectos, quiero registrar las tecnologías que usa el equipo del cliente y los tipos de usuarios del sistema, para que los criterios de aceptación generados sean aplicables al contexto real del proyecto. | **3** | -| **21** | **US20** | Cargar documentos del cliente | Como miembro con permiso para configurar proyectos, quiero subir documentos y glosarios del cliente al proyecto, para que las historias generadas reflejen el vocabulario y las reglas del negocio de ese cliente de forma observable en cada historia producida. | **5** | -| **22** | **US24** | Proyecto de demostración automático | Como nuevo usuario que acaba de registrarse, quiero encontrar un proyecto de demostración pre-cargado con audios e historias ya generadas, para entender inmediatamente el valor de la plataforma sin tener que grabar mi propia reunión primero. | **3** | -| **23** | **US22** | Editar proyecto | Como miembro con permiso para administrar proyectos, quiero modificar el nombre o la descripción de un proyecto existente, para corregir o actualizar la información cuando cambian los acuerdos con el cliente. | **1** | -| **24** | **US23** | Archivar proyecto | Como miembro con permiso para administrar proyectos, quiero archivar un proyecto cuando termina el trabajo con ese cliente, para mantener el panel principal ordenado sin eliminar el historial de sesiones e historias generadas. | **2** | -| **25** | **US49** | Resumen de cierre de sesión | Como analista al cerrar una sesión, quiero ver un resumen con el total de historias creadas, descartadas y pendientes de revisión, para confirmar con el cliente que los acuerdos quedaron correctamente registrados antes de dar la reunión por concluida. | **2** | -| **26** | **US50** | Compartir historias con el cliente | Como analista, quiero generar un enlace público y seguro de solo lectura con las historias generadas, para enviarlo al cliente y que pueda validarlas o comentarlas sin necesidad de crearse una cuenta en la plataforma. | **3** | -| **27** | **US51** | Calificar calidad de historia (Feedback Loop) | Como analista revisando las historias generadas, quiero calificar la precisión de la IA (ej. Pulgar arriba/abajo) e indicar si hubo alucinaciones, para generar un registro de retroalimentación que mejore los prompts y el modelo en el futuro. | **2** | -| **28** | **TS65** | API: Auth Jira (GET /integrations/jira/auth) | Como Developer, quiero un endpoint para iniciar el flujo OAuth2 con Atlassian. | **3** | -| **29** | **TS66** | API: Exportar a Jira (POST /integrations/jira/export) | Como Developer, quiero un endpoint que mapee y envíe las historias al Webhook de Jira. | **5** | -| **30** | **US52** | Conectar cuenta de Jira | Como miembro con permiso para configurar integraciones, quiero autorizar la conexión con Jira a través de un proceso seguro, para que la exportación de historias no requiera compartir credenciales con nadie del equipo. | **5** | -| **31** | **US53** | Configurar mapeo de proyecto en Jira | Como administrador, quiero vincular un proyecto local de Reqs-AI con un Board/Proyecto específico de Jira, para que el sistema sepa exactamente a qué destino enviar las historias durante la exportación. | **2** | -| **32** | **US54** | Exportar historias a Jira | Como miembro con permiso para exportar historias, quiero enviar las historias aprobadas directamente al backlog de Jira, para que el equipo de desarrollo pueda planificar el sprint sin copiar ni pegar nada manualmente. Depende de: US38 y US41. | **5** | -| **33** | **US25** | Invitar miembro a la organización | Como miembro con permiso para gestionar el equipo, quiero invitar a un colega mediante su correo electrónico, para que pueda acceder a los proyectos que le correspondan dentro de la organización. | **3** | -| **34** | **US26** | Crear rol personalizado | Como Propietario de la organización, quiero crear un rol con un nombre definido por mi equipo, para representar una función real dentro de la organización en lugar de usar etiquetas genéricas del sistema. | **2** | -| **35** | **US27** | Asignar permisos a un rol | Como Propietario de la organización, quiero seleccionar qué acciones puede realizar cada rol dentro de los proyectos y la organización, para que el nivel de acceso de cada miembro refleje exactamente sus responsabilidades reales. | **3** | -| **36** | **US28** | Asignar rol a un miembro | Como miembro con permiso para gestionar el equipo, quiero asignar uno de los roles disponibles a un miembro de la organización, para que sus permisos queden definidos en el momento en que acepta la invitación. Depende de: US19 y US20. | **1** | -| **37** | **US29** | Editar permisos de un rol existente | Como Propietario de la organización, quiero modificar los permisos de un rol ya creado, para ajustar el nivel de acceso de todos los miembros que lo tienen asignado cuando cambian sus responsabilidades. | **2** | -| **38** | **US30** | Eliminar un rol | Como Propietario de la organización, quiero eliminar un rol que ya no corresponde a ninguna función activa del equipo, para mantener la lista de roles ordenada y evitar asignaciones incorrectas. | **1** | -| **39** | **US31** | Remover miembro de la organización | Como miembro con permiso para gestionar el equipo, quiero remover a un miembro de la organización, para revocar su acceso a todos los proyectos de forma inmediata cuando ya no forma parte del equipo. | **2** | -| **40** | **TS55** | API: Registro de Usuario (POST /auth/register) | Como Developer, quiero un endpoint para registrar usuarios hasheando su contraseña, para asegurar sus accesos. | **2** | -| **41** | **TS56** | API: Login de Usuario (POST /auth/login) | Como Developer, quiero un endpoint que valide credenciales y retorne un JWT. | **2** | -| **42** | **TS57** | API: Perfil de Usuario (GET /auth/me) | Como Developer, quiero un endpoint que retorne los datos del usuario logueado según su JWT. | **1** | -| **43** | **US05** | Registro de cuenta | Como visitante que llega por primera vez a la plataforma, quiero crear una cuenta con mi correo y una contraseña, para poder acceder a mis proyectos y sesiones de captura de requisitos. | **3** | -| **44** | **US06** | Verificación de correo | Como usuario con cuenta pendiente de activación, quiero verificar mi correo haciendo clic en el enlace que recibí, para activar mi cuenta y empezar a trabajar en la plataforma. | **2** | -| **45** | **US07** | Inicio de sesión | Como usuario con cuenta activa, quiero iniciar sesión con mi correo y contraseña, para acceder a mis proyectos y al historial de sesiones de mi organización. | **3** | -| **46** | **US08** | Recuperación de contraseña | Como usuario registrado que no recuerda su contraseña, quiero recibir un enlace de restablecimiento en mi correo, para recuperar el acceso a mi cuenta sin perder mi historial de trabajo. | **2** | -| **47** | **US09** | Crear organización | Como usuario autenticado que aún no pertenece a ninguna organización, quiero crear un espacio de trabajo con el nombre de mi empresa, para centralizar mis proyectos y gestionar mi equipo en un entorno separado. | **2** | -| **48** | **US10** | Editar organización | Como Propietario de la organización, quiero actualizar el nombre o los datos generales de mi organización, para mantener la información correcta cuando el equipo o el negocio cambia. | **1** | -| **49** | **US11** | Cambiar de organización | Como usuario que pertenece a más de una organización, quiero seleccionar con cuál quiero trabajar desde el menú principal, para asegurarme de estar operando en el contexto correcto según el cliente que estoy atendiendo. | **2** | -| **50** | **TS58** | API: Crear Proyecto (POST /projects) | Como Developer, quiero un endpoint para crear un nuevo espacio de trabajo. | **2** | -| **51** | **TS59** | API: Listar Proyectos (GET /projects) | Como Developer, quiero un endpoint para listar los proyectos del usuario autenticado. | **1** | -| **52** | **TS60** | API: Actualizar Proyecto (PUT /projects/{id}) | Como Developer, quiero un endpoint para modificar metadatos de un proyecto. | **1** | -| **53** | **TS61** | API: Eliminar Proyecto (DELETE /projects/{id}) | Como Developer, quiero un endpoint de soft-delete para archivar proyectos. | **1** | -| **54** | **US12** | Política de retención de audios | Como Propietario de la organización, quiero configurar la eliminación automática de los archivos de audio originales X días después de procesarse, para cumplir con las políticas de privacidad y confidencialidad corporativas. | **3** | -| **55** | **US13** | Plan gratuito automático | Como usuario que acaba de crear su organización, quiero empezar a usar la plataforma sin ingresar datos de pago, para evaluar si se ajusta a mis necesidades antes de decidir suscribirme. | **2** | -| **56** | **US14** | Suscripción al plan Pro | Como Propietario de una organización en plan gratuito, quiero contratar el plan Pro, para acceder a sesiones ilimitadas, captura en tiempo real e integración con Jira. | **5** | -| **57** | **US15** | Suscripción al plan Equipo | Como Propietario de una organización en plan Pro, quiero contratar el plan Equipo, para poder agregar a todos los miembros de mi equipo y definir mediante roles personalizados qué puede hacer cada uno. | **3** | -| **58** | **US16** | Cancelación de suscripción | Como Propietario de una suscripción activa, quiero cancelar mi plan antes de que inicie el siguiente período de facturación, para no recibir cargos adicionales después de dejar de usar el servicio. | **2** | -| **59** | **US17** | Ver estado del plan y consumo | Como Propietario de la organización, quiero ver el plan activo, la fecha de renovación y cuántas sesiones o proyectos he usado, para saber cuándo necesito actualizar mi plan antes de quedarme sin cupo. | **3** | -| **60** | **US18** | Dashboard de valor y ahorro de tiempo | Como Propietario de la organización, quiero ver un panel que traduzca las historias generadas en 'horas de trabajo manual ahorradas', para justificar el pago de la suscripción y entender el retorno de inversión mensual de la herramienta. | **5** | +| # Orden | User Story Id | Título | Descripción | Story Points (1/2/3/5/8) | +|:-------:|:--------------|:-------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------:| +| **1** | **US01** | Visualizar Propuesta de Valor (Hero) | Como visitante, quiero entender inmediatamente qué es Reqs-AI y su propuesta de valor principal en la cabecera (Hero), para decidir en los primeros segundos si me interesa continuar explorando la herramienta. | **3** | +| **2** | **US02** | Explorar Casos de Uso por Segmento | Como visitante, quiero alternar entre diferentes perfiles (ej. Consultoras vs Startups) en una sección interactiva, para ver beneficios y ejemplos específicos que se adapten a la realidad de mi equipo. | **3** | +| **3** | **US03** | Visualizar Planes y Precios | Como visitante evaluando la viabilidad financiera, quiero comparar los planes de suscripción (Gratuito, Pro, Equipo) de forma transparente, para determinar cuál se ajusta a mi presupuesto antes de crearme una cuenta. | **2** | +| **4** | **TS01** | API: Registro de Usuario (POST /auth/register) | Como Developer, quiero un endpoint para registrar usuarios hasheando su contraseña, para asegurar sus accesos. | **2** | +| **5** | **TS02** | API: Login de Usuario (POST /auth/login) | Como Developer, quiero un endpoint que valide credenciales y retorne un JWT. | **2** | +| **6** | **TS03** | API: Perfil de Usuario (GET /auth/me) | Como Developer, quiero un endpoint que retorne los datos del usuario logueado según su JWT. | **1** | +| **7** | **TS15** | API: Verificar Email (POST /auth/verify-email) | Como Developer, quiero un endpoint que valide el token de verificación enviado por correo. | **3** | +| **8** | **TS16** | API: Recuperar Contraseña (POST /auth/forgot-password) | Como Developer, quiero un endpoint que genere y envíe un enlace de reset de contraseña al email. | **3** | +| **9** | **TS17** | API: Actualizar Perfil de Usuario (PUT /auth/profile) | Como Developer, quiero un endpoint que permita al usuario autenticado editar nombre, avatar y preferencias. | **3** | +| **10** | **TS14** | API: Cerrar Sesión de Usuario (POST /auth/logout) | Como Developer, quiero un endpoint que invalide el token JWT activo del usuario. | **3** | +| **11** | **US04** | Registro de cuenta | Como visitante que llega por primera vez a la plataforma, quiero crear una cuenta con mi correo y una contraseña, para poder acceder a mis proyectos y sesiones de captura de requisitos. | **3** | +| **12** | **US05** | Verificación de correo | Como usuario con cuenta pendiente de activación, quiero verificar mi correo haciendo clic en el enlace que recibí, para activar mi cuenta y empezar a trabajar en la plataforma. | **2** | +| **13** | **US06** | Inicio de sesión | Como usuario con cuenta activa, quiero iniciar sesión con mi correo y contraseña, para acceder a mis proyectos y al historial de sesiones de mi organización. | **3** | +| **14** | **US07** | Recuperación de contraseña | Como usuario registrado que no recuerda su contraseña, quiero recibir un enlace de restablecimiento en mi correo, para recuperar el acceso a mi cuenta sin perder mi historial de trabajo. | **2** | +| **15** | **US08** | Cerrar sesión | Como usuario autenticado, quiero cerrar mi sesión de forma explícita, para asegurarme de que nadie más pueda acceder a mi cuenta desde el mismo dispositivo. | **1** | +| **16** | **US09** | Aceptar términos y política de privacidad | Como nuevo usuario completando el registro, quiero leer y aceptar los términos de uso y la política de privacidad antes de activar mi cuenta, para entender cómo se usan mis datos y las grabaciones de audio antes de comenzar a usar el servicio. | **1** | +| **17** | **US10** | Editar perfil de usuario | Como usuario autenticado, quiero actualizar mi nombre y foto de perfil, para que mis compañeros de equipo me identifiquen correctamente en los registros de actividad de las sesiones. | **2** | +| **18** | **TS18** | API: Crear Organización (POST /organizations) | Como Developer, quiero un endpoint que cree una nueva organización y asigne al creador como Owner. | **3** | +| **19** | **TS19** | API: Actualizar Organización (PUT /organizations/{id}) | Como Developer, quiero un endpoint para modificar nombre, logo y configuraciones de la organización. | **3** | +| **20** | **TS20** | API: Cambiar Contexto de Organización (POST /organizations/switch) | Como Developer, quiero un endpoint que cambie la organización activa en la sesión del usuario. | **3** | +| **21** | **US11** | Crear organización | Como usuario autenticado que aún no pertenece a ninguna organización, quiero crear un espacio de trabajo con el nombre de mi empresa, para centralizar mis proyectos y gestionar mi equipo en un entorno separado. | **2** | +| **22** | **US12** | Editar organización | Como Propietario de la organización, quiero actualizar el nombre o los datos generales de mi organización, para mantener la información correcta cuando el equipo o el negocio cambia. | **1** | +| **23** | **US13** | Cambiar de organización | Como usuario que pertenece a más de una organización, quiero seleccionar con cuál quiero trabajar desde el menú principal, para asegurarme de estar operando en el contexto correcto según el cliente que estoy atendiendo. | **2** | +| **24** | **US14** | Política de retención de audios | Como Propietario de la organización, quiero configurar la eliminación automática de los archivos de audio originales X días después de procesarse, para cumplir con las políticas de privacidad y confidencialidad corporativas. | **3** | +| **25** | **TS34** | API: Upgrade de Plan (POST /subscriptions/upgrade) | Como Developer, quiero un endpoint que inicie el flujo de pago con Stripe y actualice el plan al confirmar el webhook. | **3** | +| **26** | **TS35** | API: Cancelar Suscripción (DELETE /subscriptions) | Como Developer, quiero un endpoint que marque la suscripción como cancelada al final del período de facturación. | **3** | +| **27** | **US15** | Plan gratuito automático | Como usuario que acaba de crear su organización, quiero empezar a usar la plataforma sin ingresar datos de pago, para evaluar si se ajusta a mis necesidades antes de decidir suscribirme. | **2** | +| **28** | **US16** | Suscripción al plan Pro | Como Propietario de una organización en plan gratuito, quiero contratar el plan Pro, para acceder a sesiones ilimitadas, captura en tiempo real e integración con Jira. | **5** | +| **29** | **US17** | Suscripción al plan Equipo | Como Propietario de una organización en plan Pro, quiero contratar el plan Equipo, para poder agregar a todos los miembros de mi equipo y definir mediante roles personalizados qué puede hacer cada uno. | **3** | +| **30** | **US18** | Cancelación de suscripción | Como Propietario de una suscripción activa, quiero cancelar mi plan antes de que inicie el siguiente período de facturación, para no recibir cargos adicionales después de dejar de usar el servicio. | **2** | +| **31** | **US19** | Ver estado del plan y consumo | Como Propietario de la organización, quiero ver el plan activo, la fecha de renovación y cuántas sesiones o proyectos he usado, para saber cuándo necesito actualizar mi plan antes de quedarme sin cupo. | **3** | +| **32** | **TS04** | API: Crear Proyecto (POST /projects) | Como Developer, quiero un endpoint para crear un nuevo espacio de trabajo. | **2** | +| **33** | **TS05** | API: Listar Proyectos (GET /projects) | Como Developer, quiero un endpoint para listar los proyectos del usuario autenticado. | **1** | +| **34** | **TS06** | API: Actualizar Proyecto (PUT /projects/{id}) | Como Developer, quiero un endpoint para modificar metadatos de un proyecto. | **1** | +| **35** | **TS07** | API: Eliminar Proyecto (DELETE /projects/{id}) | Como Developer, quiero un endpoint de soft-delete para archivar proyectos. | **1** | +| **36** | **TS31** | API: Cargar Documento (POST /projects/{id}/documents) | Como Developer, quiero un endpoint multipart que acepte PDF/Word, extraiga el texto con Apache Tika y genere embeddings con el LLM. | **3** | +| **37** | **TS32** | API: Agregar Término al Glosario (POST /projects/{id}/glossary) | Como Developer, quiero un endpoint que persista un término con su definición en el contexto de un proyecto. | **3** | +| **38** | **TS33** | API: Agregar Restricción Técnica (POST /projects/{id}/constraints) | Como Developer, quiero un endpoint que registre una restricción técnica asociada al proyecto para enriquecer el contexto del RAG. | **3** | +| **39** | **US20** | Crear proyecto | Como analista, quiero registrar un proyecto asociado a un cliente específico, para organizar y separar todas las sesiones de levantamiento de requisitos de ese cliente. | **2** | +| **40** | **US21** | Cargar documentos del cliente | Como analista, quiero subir documentos del cliente (PDF, Word) al proyecto, para que el sistema extraiga y clasifique automáticamente su contenido en glosario, restricciones y contexto general, enriqueciendo así las historias que se generen en sesiones futuras. | **5** | +| **41** | **US22** | Configurar perfil técnico del proyecto | Como analista, quiero registrar las tecnologías que usa el equipo del cliente y los tipos de usuarios del sistema, para que los criterios de aceptación generados sean aplicables al contexto real del proyecto. | **3** | +| **42** | **US23** | Agregar término al glosario manualmente | Como analista, quiero agregar términos del negocio del cliente directamente al glosario del proyecto sin necesidad de subir un documento, para enriquecer el vocabulario de la IA cuando el cliente solo transmite su conocimiento de forma oral. | **2** | +| **43** | **US24** | Registrar restricción técnica manualmente | Como analista, quiero registrar restricciones técnicas del cliente (ej. 'sin uso de cookies de terceros', 'máximo 3 segundos de latencia') directamente en el proyecto, para que la IA las considere al generar los criterios de aceptación sin necesidad de incluirlas en cada sesión. | **2** | +| **44** | **US25** | Editar proyecto | Como analista, quiero modificar el nombre o la descripción de un proyecto existente, para corregir o actualizar la información cuando cambian los acuerdos con el cliente. | **1** | +| **45** | **US26** | Archivar proyecto | Como analista, quiero archivar un proyecto cuando termina el trabajo con ese cliente, para mantener el panel principal ordenado sin eliminar el historial de sesiones e historias generadas. | **2** | +| **46** | **US27** | Proyecto de demostración automático | Como nuevo usuario que acaba de registrarse, quiero encontrar un proyecto de demostración pre-cargado con audios e historias ya generadas, para entender inmediatamente el valor de la plataforma sin tener que grabar mi propia reunión primero. | **3** | +| **47** | **TS21** | API: Invitar Miembro (POST /organizations/{id}/invitations) | Como Developer, quiero un endpoint que genere y envíe una invitación por email para unirse a la organización. | **3** | +| **48** | **TS22** | API: Crear Rol Personalizado (POST /organizations/{id}/roles) | Como Developer, quiero un endpoint que registre un nuevo rol con nombre y conjunto de permisos. | **3** | +| **49** | **TS23** | API: Configurar Permisos de Rol (PUT /organizations/{id}/roles/{roleId}) | Como Developer, quiero un endpoint que actualice los permisos asociados a un rol existente. | **3** | +| **50** | **TS24** | API: Eliminar Rol (DELETE /organizations/{id}/roles/{roleId}) | Como Developer, quiero un endpoint que elimine un rol personalizado si no tiene miembros asignados. | **3** | +| **51** | **US28** | Invitar miembro a la organización | Como Administrador de la organización, quiero invitar a un colega mediante su correo electrónico, para que pueda acceder a los proyectos que le correspondan dentro de la organización. | **3** | +| **52** | **US29** | Crear rol personalizado | Como Propietario de la organización, quiero crear un rol con un nombre definido por mi equipo, para representar una función real dentro de la organización en lugar de usar etiquetas genéricas del sistema. | **2** | +| **53** | **US30** | Asignar permisos a un rol | Como Propietario de la organización, quiero seleccionar qué acciones puede realizar cada rol dentro de los proyectos y la organización, para que el nivel de acceso de cada miembro refleje exactamente sus responsabilidades reales. | **3** | +| **54** | **US31** | Asignar rol a un miembro | Como Administrador de la organización, quiero asignar uno de los roles disponibles a un miembro de la organización, para que sus permisos queden definidos en el momento en que acepta la invitación. | **1** | +| **55** | **US32** | Editar permisos de un rol existente | Como Propietario de la organización, quiero modificar los permisos de un rol ya creado, para ajustar el nivel de acceso de todos los miembros que lo tienen asignado cuando cambian sus responsabilidades. | **2** | +| **56** | **US33** | Eliminar un rol | Como Propietario de la organización, quiero eliminar un rol que ya no corresponde a ninguna función activa del equipo, para mantener la lista de roles ordenada y evitar asignaciones incorrectas. | **1** | +| **57** | **US34** | Remover miembro de la organización | Como Administrador de la organización, quiero remover a un miembro de la organización, para revocar su acceso a todos los proyectos de forma inmediata cuando ya no forma parte del equipo. | **2** | +| **58** | **US35** | Transferir propiedad de la organización | Como Propietario de la organización, quiero transferir la propiedad a otro miembro activo, para poder desvincularme del equipo sin dejar la cuenta sin responsable cuando abandono el proyecto. | **3** | +| **59** | **TS13** | Servicio interno: Chunking de audio para ventana de tokens del LLM | Como Developer, quiero que el pipeline de procesamiento divida automáticamente los audios largos en fragmentos semánticos manejables antes de enviarlos al LLM, para evitar errores de límite de tokens y garantizar coherencia en reuniones extensas. | **3** | +| **60** | **TS25** | API: Crear Sesión en Vivo (POST /sessions) | Como Developer, quiero un endpoint que inicialice una sesión de captura en vivo asociada a un proyecto. | **3** | +| **61** | **TS26** | API: Cambiar Estado de Sesión (PATCH /sessions/{id}/status) | Como Developer, quiero un endpoint que cambie el estado de una sesión entre OPEN, PAUSED y CLOSED. | **3** | +| **62** | **US36** | Iniciar sesión de captura en vivo | Como analista, quiero activar la captura de audio durante la reunión dentro de un proyecto existente, para que el sistema identifique quién habla en cada momento y genere las historias a partir de lo que dice el cliente sin que yo tenga que tomar notas. Depende de: US20. | **3** | +| **63** | **US37** | Generación automática de historias | Como analista en sesión activa, quiero que el sistema convierta automáticamente lo que dice el cliente en historias de usuario con criterios de aceptación, para obtener un backlog estructurado al finalizar la reunión sin necesidad de documentar después. | **8** | +| **64** | **US38** | Descartar historia durante sesión en vivo | Como analista con una sesión activa, quiero descartar una historia recién generada indicando un motivo, para mantener el backlog de la sesión limpio en tiempo real sin interrumpir la reunión. | **2** | +| **65** | **US39** | Descartar historia en revisión post-sesión | Como analista revisando el backlog después de cerrar la sesión, quiero descartar una historia indicando un motivo, para depurar el resultado final antes de que el equipo de desarrollo lo utilice. | **2** | +| **66** | **US40** | Consentimiento de grabación al iniciar sesión | Como analista que va a iniciar una sesión de captura, quiero que el sistema muestre un aviso claro indicando que se grabará audio antes de comenzar, para que todos los participantes sean informados y puedan dar su consentimiento. | **2** | +| **67** | **US41** | Pausar y reanudar captura | Como analista durante una sesión activa, quiero pausar la captura cuando la conversación se desvía del tema o hay un receso, para evitar que se generen historias a partir de conversaciones que no son requisitos del cliente. | **2** | +| **68** | **US42** | Cerrar y guardar sesión | Como analista al terminar una reunión, quiero finalizar la sesión de captura, para asegurar que todas las historias generadas queden guardadas en el historial del proyecto. | **2** | +| **69** | **US43** | Abortar sesión sin guardar | Como analista en una sesión de captura, quiero poder abortar y descartar la reunión por completo si hubo un error (ej. reunión cancelada), para no ensuciar el historial del proyecto con datos basura. | **2** | +| **70** | **US44** | Etiquetar voz del cliente (Diarización) | Como analista revisando una sesión, quiero poder identificar y etiquetar qué voz corresponde al 'Cliente' y cuáles al 'Equipo', para que la IA priorice las necesidades del cliente y no genere historias basadas en nuestras propias preguntas. | **5** | +| **71** | **TS08** | API: Subir Audio (POST /sessions/upload) | Como Developer, quiero un endpoint que reciba un multipart/form-data y lo suba a Cloud Storage. | **3** | +| **72** | **TS09** | API: Transcribir Audio (POST /sessions/transcribe) | Como Developer, quiero un endpoint que envíe la URL del audio a Whisper/AWS y retorne texto. | **5** | +| **73** | **TS10** | API: Extraer Historias (POST /sessions/extract) | Como Developer, quiero un endpoint que procese la transcripción con el LLM y retorne el JSON de historias. | **5** | +| **74** | **US45** | Subir grabación de reunión | Como analista, quiero subir el archivo de audio de una reunión pasada dentro de un proyecto específico, para que el sistema extraiga los requisitos aplicando el glosario y contexto técnico de ese proyecto. | **3** | +| **75** | **US46** | Ver estado del procesamiento | Como analista que subió una grabación de reunión, quiero ver el avance del procesamiento de forma visible en la pantalla, para saber cuándo las historias están listas sin tener que revisar la sección manualmente cada cierto tiempo. Depende de: US45. | **2** | +| **76** | **US47** | Ver historial de sesiones del proyecto | Como miembro del equipo, quiero ver el listado de todas las sesiones (en vivo y grabaciones) asociadas a un proyecto, para acceder rápidamente al resultado de reuniones anteriores sin tener que recordar fechas exactas. | **2** | +| **77** | **US48** | Sugerencias de preguntas al cliente | Como analista durante o después de una sesión, quiero recibir una lista de preguntas concretas cuando el sistema detecta partes ambiguas en lo que dijo el cliente, para hacer esas preguntas antes de cerrar la reunión y evitar contactar al cliente después. Depende de: US37. | **5** | +| **78** | **US49** | Detección de casos no mencionados | Como analista revisando las historias generadas, quiero ver una lista de situaciones concretas que no se mencionaron en la reunión pero que suelen presentarse en ese tipo de módulo, para que el equipo las evalúe antes de empezar a construir y no las descubra durante el desarrollo. Depende de: US37. | **3** | +| **79** | **US50** | Detección de historias similares | Como analista en una sesión activa, quiero que el sistema me avise con el porcentaje de similitud cuando una historia nueva se parece a una que ya existe en el proyecto, para evitar que el backlog tenga requisitos redundantes o contradictorios entre sí. | **5** | +| **80** | **US51** | Resolver historia duplicada | Como analista que recibió una alerta de similitud, quiero decidir si fusiono la nueva historia con la existente o la mantengo separada dejando registrada mi justificación, para que el backlog quede limpio con una decisión explícita visible en el historial. Depende de: US50. | **3** | +| **81** | **TS28** | API: Listar y Buscar Historias (GET /projects/{id}/stories) | Como Developer, quiero un endpoint con filtros por estado, épica y texto para recuperar historias de un proyecto. | **3** | +| **82** | **TS29** | API: Editar Historia (PUT /stories/{id}) | Como Developer, quiero un endpoint que permita actualizar título, descripción y criterios de aceptación de una historia. | **3** | +| **83** | **TS30** | API: Cambiar Estado de Historia (PATCH /stories/{id}/status) | Como Developer, quiero un endpoint que cambie el estado de una historia entre DRAFT, APPROVED y REJECTED. | **3** | +| **84** | **TS27** | API: Listar Sesiones de Proyecto (GET /projects/{id}/sessions) | Como Developer, quiero un endpoint paginado que retorne todas las sesiones de un proyecto ordenadas por fecha. | **3** | +| **85** | **US52** | Editar historia generada | Como analista, quiero modificar el texto de una historia generada por la IA, para ajustar el lenguaje al estándar que usa mi equipo antes de aprobarla. | **3** | +| **86** | **US53** | Aprobar historia | Como analista, quiero marcar una historia como aprobada después de revisarla, para que quede disponible para el equipo de desarrollo y pueda exportarse al gestor de tareas. | **2** | +| **87** | **US54** | Resumen de cierre de sesión | Como analista al cerrar una sesión, quiero ver un resumen con el total de historias creadas, descartadas y pendientes de revisión, para confirmar con el cliente que los acuerdos quedaron correctamente registrados antes de dar la reunión por concluida. | **2** | +| **88** | **US55** | Compartir historias con el cliente | Como analista, quiero generar un enlace público y seguro de solo lectura con las historias generadas, para enviarlo al cliente y que pueda validarlas o comentarlas sin necesidad de crearse una cuenta en la plataforma. | **3** | +| **89** | **US56** | Calificar calidad de historia (Feedback Loop) | Como analista revisando las historias generadas, quiero calificar la precisión de la IA (ej. Pulgar arriba/abajo) e indicar si hubo alucinaciones, para generar un registro de retroalimentación que mejore los prompts y el modelo en el futuro. | **2** | +| **90** | **US57** | Buscar y filtrar historias del backlog | Como analista revisando el backlog de un proyecto, quiero buscar historias por texto libre y filtrarlas por estado (generada, aprobada, descartada) o épica, para localizar rápidamente las historias que necesito revisar sin tener que desplazarme por todo el backlog. | **2** | +| **91** | **TS11** | API: Auth Jira (GET /integrations/jira/auth) | Como Developer, quiero un endpoint para iniciar el flujo OAuth2 con Atlassian. | **3** | +| **92** | **TS12** | API: Exportar a Jira (POST /integrations/jira/export) | Como Developer, quiero un endpoint que mapee y envíe las historias al Webhook de Jira. | **5** | +| **93** | **US58** | Conectar cuenta de Jira | Como Administrador de la organización, quiero autorizar la conexión con Jira a través de un proceso seguro, para que la exportación de historias no requiera compartir credenciales con nadie del equipo. | **5** | +| **94** | **US59** | Configurar mapeo de proyecto en Jira | Como Administrador de la organización, quiero vincular un proyecto local de Reqs-AI con un Board/Proyecto específico de Jira, para que el sistema sepa exactamente a qué destino enviar las historias durante la exportación. | **2** | +| **95** | **US60** | Exportar historias a Jira | Como analista, quiero enviar las historias aprobadas directamente al backlog de Jira, para que el equipo de desarrollo pueda planificar el sprint sin copiar ni pegar nada manualmente. Depende de: US53 y US58. | **5** | **Referencia en Herramienta de Gestión (Jira Software)** @@ -1049,7 +1149,7 @@ La trazabilidad final se mantiene en formato **Goal -> Persona -> Impact -> Deli ## 4.1. Strategic-Level Attribute-Driven Design -### 4.1.1. Design Purpose +### 4.1.1. Design Purpose El propósito del proceso de diseño de Reqs-AI es establecer una arquitectura de software capaz de resolver tecnológicamente la desconexión y pérdida de información que ocurre durante el levantamiento de requisitos. El diseño está concebido como un sistema creado desde cero orientado a construir una solución robusta, escalable y en tiempo real que traduzca la voz del cliente directamente en especificaciones técnicas de alto valor. @@ -1059,21 +1159,27 @@ Este diseño está directamente orientado a satisfacer las necesidades críticas A nivel de negocio para la startup Kntro-Soft, el diseño tiene el propósito de habilitar el modelo de distribución SaaS (Software as a Service). La arquitectura debe soportar el sistema de suscripciones y facturación, gestionar los límites de consumo de los motores de IA (LLM) para mantener la rentabilidad, y asegurar que la plataforma pueda escalar el procesamiento de múltiples organizaciones concurrentes sin degradar la experiencia de usuario. -### 4.1.2. Attribute-Driven Design Inputs +### 4.1.2. Attribute-Driven Design Inputs En esta sección se presentan las entradas fundamentales requeridas para ejecutar el proceso de diseño arquitectónico basado en el método Attribute-Driven Design (ADD). Estos inputs proporcionan el contexto, las metas y los límites que guiarán la toma de decisiones técnicas para la plataforma Reqs-AI. A continuación, el contenido se divide en tres categorías principales dictadas por la metodología: la funcionalidad primaria, los escenarios de atributos de calidad y las restricciones. -#### 4.1.2.1. Primary Functionality (Primary User Stories) +#### 4.1.2.1. Primary Functionality (Primary User Stories) A continuación, se detallan las Historias de Usuario primarias que tienen el mayor impacto arquitectónico en el diseño de Reqs-AI. Estas funcionalidades han sido seleccionadas porque introducen requerimientos complejos de procesamiento asíncrono (análisis de audio en tiempo real), integración con servicios de Inteligencia Artificial (LLM) y aislamiento estricto de datos (Multitenancy), elementos que dictarán la topología base del sistema. | Epic / User Story ID | Título | Descripción | Criterios de Aceptación | Relacionado con (Epic ID) | |:---------------------|:-----------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------| -| **US05** | Crear organización | Como usuario autenticado sin organización, quiero crear un espacio de trabajo con el nombre de mi empresa, para centralizar proyectos en un entorno separado. | **Given** un usuario autenticado sin organización
**When** crea una organización con un nombre válido
**Then** el sistema genera el espacio de trabajo
**And** asigna al usuario el rol inamovible de 'Propietario' | EP02 | -| **US16** | Cargar documentos del cliente | Como miembro, quiero subir documentos del cliente al proyecto, para que las historias generadas reflejen el vocabulario del negocio (RAG). | **Given** un usuario configurando un proyecto
**When** sube un glosario en PDF válido
**Then** el sistema debe fragmentar (chunking), vectorizar y persistir el documento en una base de datos vectorial en menos de 10 segundos | EP04 | -| **US29** | Generación automática de historias | Como analista, quiero que el sistema procese el audio en vivo y redacte automáticamente historias de usuario estructuradas con criterios de aceptación, para ahorrar horas de redacción manual. | **Given** una sesión de captura de audio activa
**When** el sistema recibe y transcribe el flujo de voz
**Then** el motor de IA procesa el texto generado
**And** redacta una historia de usuario en formato Gherkin
**And** la añade al backlog de la sesión actual | EP06 | +| **US11** | Crear organización | Como usuario autenticado sin organización, quiero crear un espacio de trabajo con el nombre de mi empresa, para centralizar proyectos en un entorno separado. | **Given** un usuario autenticado sin organización
**When** crea una organización con un nombre válido
**Then** el sistema genera el espacio de trabajo
**And** asigna al usuario el rol inamovible de 'Propietario' | EP02 | +| **US21** | Cargar documentos del cliente | Como miembro, quiero subir documentos del cliente al proyecto, para que las historias generadas reflejen el vocabulario del negocio (RAG). | **Given** un usuario configurando un proyecto
**When** sube un glosario en PDF válido
**Then** el sistema debe fragmentar (chunking), vectorizar y persistir el documento en una base de datos vectorial en menos de 10 segundos | EP04 | +| **US37** | Generación automática de historias | Como analista, quiero que el sistema procese el audio en vivo y redacte automáticamente historias de usuario estructuradas con criterios de aceptación, para ahorrar horas de redacción manual. | **Given** una sesión de captura de audio activa
**When** el sistema recibe y transcribe el flujo de voz
**Then** el motor de IA procesa el texto generado
**And** redacta una historia de usuario en formato Gherkin
**And** la añade al backlog de la sesión actual | EP06 | -#### 4.1.2.2. Quality attribute Scenarios +**Justificación del Impacto Arquitectónico:** + +* **US11 (Crear organización):** Define el modelo base de multi-tenant y la asignación de ownership; requiere aislamiento de datos, provisión de recursos por organización y reglas consistentes para separar el acceso entre organizaciones. +* **US21 (Cargar documentos del cliente):** Obliga a definir un pipeline de ingesta asíncrona con chunking, embeddings y almacenamiento vectorial, controlando tiempos de procesamiento, costos y límites por organización. +* **US37 (Generación automática de historias):** Exige procesamiento en tiempo real con baja latencia, orquestación de STT y LLM, y manejo de estados de sesión y reintentos sin perder datos durante la captura en vivo. + +#### 4.1.2.2. Quality attribute Scenarios En esta sección se definen los escenarios de atributos de calidad más críticos que guiarán las decisiones arquitectónicas de Reqs-AI. Se ha priorizado el Rendimiento (necesario para el procesamiento de audio en tiempo real), la Seguridad (aislamiento de datos), la Disponibilidad (para tolerar fallas externas) y la Modificabilidad (cambio de proveedores de IA). @@ -1085,17 +1191,23 @@ En esta sección se definen los escenarios de atributos de calidad más crítico | **Availability (Disponibilidad)** | Proveedor externo de IA (API) | El servicio del LLM no responde (Timeout) o devuelve un error 500 durante una sesión en vivo. | Motor de Integración de IA | Entorno degradado (Falla de dependencia externa). | El sistema persiste la transcripción con estado Pendiente y encola la solicitud en memoria mediante Eventos de Spring asíncronos para reintentar la conexión. | Se recupera la operación con **0 bytes perdidos**. | | **Modifiability (Modificabilidad)** | Arquitecto de Software | El negocio decide cambiar de proveedor de LLM (ej. de OpenAI a Anthropic) por un incremento de precios. | Módulo de Integración de IA | Tiempo de diseño/desarrollo. | El desarrollador implementa un nuevo adaptador (Adapter) para la nueva API sin alterar la lógica de negocio core. | El cambio se completa e integra en **menos de 16 horas de desarrollo**. | -##### 4.1.2.3. Constraints +#### 4.1.2.3. Constraints + +Esta sección describe las restricciones innegociables impuestas por el modelo de negocio, las capacidades técnicas del equipo y la viabilidad del proyecto. Las principales restricciones incluyen la dependencia de API de LLM externos y el uso del stack tecnológico (Java/Spring Boot y Angular/Vue.js). A continuación, se detallan: + +| Constraint ID | Título | Descripción | Criterios de Aceptación | Relacionado con (Epic ID) | +|:--------------|:-------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------| +| **CON-01** | Stack Tecnológico Backend y Frontend | Como equipo de desarrollo, debemos utilizar Java (Spring Boot) para el backend y Angular o Vue.js para el frontend. | **Given** un nuevo componente a desarrollar
**When** el equipo inicie su construcción
**Then** el código debe estar escrito en Java 17+ usando Spring Boot o TypeScript usando Angular/Vue.js. | EP01, EP02 | +| **CON-02** | Uso de APIs de LLM externas | Como Arquitecto de Software, debo integrar el sistema con APIs de modelos de terceros (ej. OpenAI, Anthropic), ya que no alojaremos modelos propios. | **Given** la necesidad de generar Gherkin
**When** el sistema realice una inferencia
**Then** la petición debe enrutarse hacia la API REST del proveedor seleccionado. | EP04 | +| **CON-03** | Despliegue en Cloud Pública | Como responsable de infraestructura, debo asegurar que los componentes sean desplegados en la nube (AWS, Azure o GCP), para evitar costos *on-premise*. | **Given** la liberación de una nueva versión
**When** se ejecute el pipeline de despliegue
**Then** los artefactos deben aprovisionarse en la nube pública seleccionada. | Todas | -Esta sección describe las restricciones innegociables impuestas por el modelo de negocio, las capacidades técnicas del equipo y la viabilidad del proyecto. Las principales restricciones incluyen la dependencia de API de LLM externos y el uso del stack tecnológico (Java/Spring Boot y Angular/Vue.js). A continuación, se detallan como Technical Stories: +**Justificación de Restricciones:** -| Technical Story ID | Título | Descripción | Criterios de Aceptación | Relacionado con (Epic ID) | -|:-------------------|:-------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------| -| **TS01** | Stack Tecnológico Backend y Frontend | Como equipo de desarrollo, debemos utilizar Java (Spring Boot) para el backend y Angular o Vue.js para el frontend, debido a la experiencia técnica previa del equipo. | **Given** un nuevo componente a desarrollar
**When** el equipo inicie su construcción
**Then** el código debe estar escrito en Java 17+ usando Spring Boot o TypeScript usando Angular/Vue.js. | EP01, EP02 | -| **TS02** | Uso de APIs de LLM externas | Como Arquitecto de Software, debo integrar el sistema con APIs de modelos de terceros (ej. OpenAI, Anthropic), ya que no alojaremos modelos propios. | **Given** la necesidad de generar Gherkin
**When** el sistema realice una inferencia
**Then** la petición debe enrutarse hacia la API REST del proveedor seleccionado. | EP04 | -| **TS03** | Despliegue en Cloud Pública | Como responsable de infraestructura, debo asegurar que los componentes sean desplegados en la nube (AWS, Azure o GCP), para evitar costos *on-premise*. | **Given** la liberación de una nueva versión
**When** se ejecute el pipeline de despliegue
**Then** los artefactos deben aprovisionarse en la nube pública seleccionada. | Todas | +* **CON-01 (Stack Tecnológico Backend y Frontend):** El equipo tiene experiencia consolidada en Java/Spring y frameworks web, lo que reduce curva de aprendizaje y riesgo de entrega. +* **CON-02 (Uso de API de LLM externas):** Los modelos propios no son viables por costos de infraestructura, mantenimiento y talento especializado para entrenamiento y hosting. +* **CON-03 (Despliegue en Cloud Pública):** La startup necesita reducir costos de inversión inicial y operar con elasticidad sin invertir en infraestructura propia. -### 4.1.3. Architectural Drivers Backlog +### 4.1.3. Architectural Drivers Backlog En esta sección se establece el conjunto de Architectural Drivers acordados por el equipo, resultado del proceso iterativo en nuestro Quality Attribute Workshop (QAW). El Architectural Drivers Backlog consolida los Functional Drivers seleccionados (provenientes de las historias de usuario críticas), los Quality Attribute Drivers seleccionados (Performance, Security, Availability, Modifiability) y todos los Constraints del negocio y la tecnología. Todos los Drivers se presentan a continuación, habiendo sido priorizados de forma descendente colocando primero aquellos que representan una Alta importancia para los Stakeholders y un Alto impacto en la Complejidad Técnica de la Arquitectura. @@ -1110,7 +1222,7 @@ En esta sección se establece el conjunto de Architectural Drivers acordados por | **AD-07** | Despliegue en Cloud Pública | Todos los componentes deben ser contenerizados y desplegados en una nube pública como AWS o Azure para minimizar costos *on-premise* y permitir la escalabilidad. | Medium | Medium | | **AD-08** | Stack Tecnológico Base de Desarrollo | El backend debe desarrollarse en Java (Spring Boot) y el frontend en Angular/Vue.js debido al conocimiento técnico previo del equipo, limitando la adopción de otros lenguajes core. | Medium | Low | -### 4.1.4. Architectural Design Decisions +### 4.1.4. Architectural Design Decisions En esta sección se detalla el proceso seguido durante las iteraciones (Stages) del *Quality Attribute Workshop* para definir la arquitectura de Reqs-AI. En cada etapa, el equipo enfrentó un conjunto específico de *Architectural Drivers* y evaluó distintos patrones o tácticas de diseño, sopesando sus pros y contras (Trade-offs) para asegurar que la solución cumpla con los atributos de calidad exigidos sin incurrir en *over-engineering* para la etapa actual de la startup. @@ -1134,8 +1246,7 @@ La siguiente matriz resume la evaluación de los patrones candidatos considerado | **AD-03** | Tolerancia a Fallos en Servicios Externos | **Cola en Memoria RAM local (Patrón Observer con @Async)** *(Elegido)*
**Pro:** Muy rápido (latencia mínima), simplicidad de código y no requiere aprovisionar infraestructura extra (RabbitMQ/Kafka).
**Con:** Si el servidor se reinicia abruptamente, los eventos en memoria se pierden, por lo que el estado debe respaldarse en Base de Datos. | **Message Broker Persistente (ej. RabbitMQ / Kafka)** *(Descartado)*
**Pro:** Garantiza la durabilidad total de los mensajes y retries automáticos si el pod falla.
**Con:** Exceso de ingeniería (Overengineering) para la etapa actual del proyecto. Requiere aprovisionar, configurar y pagar por otro componente pesado en el cloud. | *(No se evaluó un 3er patrón)* | | **AD-06** | Modificabilidad de Proveedores IA | **Arquitectura Monolítica en Capas**
**Pro:** Modelo mental simple para el equipo (Controllers, Services, Repositories).
**Con:** Lógica de negocio altamente acoplada al SDK del proveedor de IA. Tomaría semanas migrar. *(Descartado)* | **Arquitectura Hexagonal (Ports & Adapters)** *(Elegido)*
**Pro:** El dominio ignora qué IA se usa. Cambiar a Anthropic solo exige escribir un nuevo Adapter para el Port correspondiente en <16h.
**Con:** Exige escribir más código *boilerplate* y dominar Inyección de Dependencias. | *(No se evaluó un 3er patrón)* | -### 4.1.5. Quality Attribute Scenario Refinements - +### 4.1.5. Quality Attribute Scenario Refinements Tras finalizar las iteraciones del *Quality Attribute Workshop* y definir los patrones arquitectónicos base (WebSockets para el streaming, Shared DB con Row Level Security para el multitenancy, y Arquitectura Orientada Eventos en Memoria para la tolerancia a fallos), procedemos a refinar los escenarios de atributos de calidad priorizados. Estos refinamientos incorporan los artefactos tecnológicos que ahora conocemos y mapean directamente los escenarios con los objetivos de negocio (Business Goals) de la plataforma SaaS, identificando además las preguntas abiertas y riesgos remanentes (Issues). @@ -1306,9 +1417,9 @@ A continuación, se presenta la versión final de los escenarios refinados en or -## 4.2. Strategic-Level Domain-Driven Design +## 4.2. Strategic-Level Domain-Driven Design -### 4.2.1. EventStorming +### 4.2.1. EventStorming Para establecer una base sólida en el diseño guiado por el dominio (DDD) y facilitar el descubrimiento de nuestros Bounded Contexts, el equipo de Kntro-Soft llevó a cabo una sesión de **Design-Level Event Storming** utilizando la herramienta colaborativa Miro. La sesión tuvo una duración aproximada de 2 horas. El objetivo principal fue transicionar del espacio del problema (entendido en los capítulos anteriores) al espacio de la solución, mapeando la línea de tiempo completa del negocio de Reqs-AI. Al utilizar un enfoque de nivel de diseño, pudimos no solo explorar los eventos que ocurren en el sistema (como la captura de audio o la generación de Gherkin), sino también agrupar lógicamente las reglas de negocio para descubrir los Agregados (Aggregates) estructurales del código antes de definir las fronteras de los subdominios. @@ -1354,7 +1465,7 @@ El resultado de la sesión de Design-Level Event Storming fue un mapa exhaustivo El paso final metodológico del Event Storming, que consiste en agrupar estos Agregados dentro de las fronteras lógicas de los Bounded Contexts correspondientes, se abordará en detalle en la siguiente sección, donde evaluaremos las relaciones semánticas y cohesivas para definir la arquitectura final del dominio. -### 4.2.2. Candidate Context Discovery +### 4.2.2. Candidate Context Discovery A partir del dominio modelado en nuestra sesión de EventStorming, el equipo llevó a cabo un taller colaborativo de descubrimiento de contextos (Candidate Context Discovery) de aproximadamente 2 horas. El objetivo de esta fase fue trazar fronteras lógicas alrededor de los Agregados identificados previamente, con el fin de descomponer el sistema en módulos altamente cohesivos y con bajo acoplamiento (Bounded Contexts). @@ -1373,7 +1484,6 @@ Buscamos los Eventos Pivote que marcan hitos críticos en la vida del cliente. Evaluamos las integraciones postprocesamiento. * Después de que las historias son generadas, deben enviarse a plataformas externas (ej. Jira). Para proteger nuestro Core Domain de los constantes cambios en las API de terceros, aplicamos el patrón Anti-Corruption Layer (ACL) aislando el agregado ExternalConnection en el **Integration Gateway**. - **Resumen de Bounded Contexts Descubiertos** A través de este proceso analítico y evolutivo, el sistema quedó dividido arquitectónicamente en los siguientes 5 Candidate Bounded Contexts: @@ -1387,7 +1497,7 @@ A través de este proceso analítico y evolutivo, el sistema quedó dividido arq ![CCD](assets/ddd/bounded-contexts.jpg) -### 4.2.3. Domain Message Flows Modeling +### 4.2.3. Domain Message Flows Modeling En esta sección, aplicamos una variante técnica de Domain Storytelling enfocado en el flujo de mensajes para evidenciar cómo colaboran los 5 Bounded Contexts consolidados (**Requirement Discovery**, **Workspace Management**, **IAM**, **Billing & Subscription**, e **Integration Gateway**) y así resolver los casos principales del negocio. @@ -1434,7 +1544,7 @@ Este es un flujo netamente operativo y postprocesamiento. Ocurre después de que ![Domain Message Flow](assets/ddd/domain-message-flow-3.jpg) -### 4.2.4. Bounded Context Canvases +### 4.2.4. Bounded Context Canvases En esta sección el equipo diseña sus candidate bounded contexts, detallando los criterios de diseño. El equipo seleccionó cada bounded context, por orden de importancia, para elaborar su Bounded Context Canvas. @@ -1468,7 +1578,7 @@ Capa Anticorrupción (ACL) que protege el Core Domain de los cambios en API de t ![Canvas5](assets/ddd/bc-canvas-gateway.jpg) -### 4.2.5. Context Mapping +### 4.2.5. Context Mapping En esta sección evidenciamos el proceso de elaboración de nuestro Context Map. Para llegar al diseño estructural definitivo de nuestros 5 Bounded Contexts, el equipo evaluó el modelo sometiéndolo a un análisis crítico, respondiendo a las preguntas estratégicas de diseño sugeridas. Además, aplicamos rigurosamente los patrones de integración definidos por el repositorio oficial de Context Mapping de DDD Crew. @@ -1541,36 +1651,39 @@ A continuación presentamos la visualización de las relaciones estructurales co ![Context Map](assets/ddd/context-map.png) -## 4.3. Software Architecture +## 4.3. Software Architecture En este capítulo, el equipo detalla la arquitectura de software de Reqs-AI aplicando el modelo C4. Este modelo jerárquico permite documentar el sistema desde su ecosistema más amplio hasta sus componentes desplegables y su entorno de infraestructura final. Todas las decisiones reflejadas en estos diagramas están alineadas con los Atributos de Calidad y Restricciones analizados previamente, como el uso del Monolito Modular, la tolerancia a fallos en memoria y el multitenancy seguro. -### 4.3.1. Software Architecture System Landscape Diagram +### 4.3.1. Software Architecture System Landscape Diagram El *System Landscape Diagram* proporciona una vista panorámica del ecosistema tecnológico en el que habita Reqs-AI. A diferencia de un simple diagrama de contexto que solo mira hacia adentro, este diagrama ilustra cómo nuestro sistema principal (agrupado dentro de los límites de la empresa *Kntro-Soft Enterprise*) interactúa no solo con los usuarios, sino también con el entorno de herramientas de terceros que el usuario final ya utiliza en su día a día corporativo. A continuación, se presenta la topología del paisaje del sistema: -![SystemLandscapeDiagram](assets/architecture/system-landscape.png) +![SystemLandscapeDiagram](assets/diagrams/architecture/system-landscape.png) **Análisis de Interacciones en el Ecosistema:** -1. **Límite Empresarial Kntro-Soft Enterprise:** Agrupa a nuestros actores principales (Technical Lead y Enterprise Analyst) interactuando centralmente con el **ReqsAI System**. Este es el núcleo de valor donde se graban las reuniones, se analizan los requerimientos y se estructuran en historias de usuario. -2. **Proveedores de Inteligencia y Procesamiento (Core Dependencies):** En la parte inferior, observamos las dependencias críticas (SaaS) que Reqs-AI delega para cumplir sus objetivos complejos: - * **STT API (Speech-to-Text):** Recibe los fragmentos de audio en tiempo real y devuelve el texto. - * **LLM API (Large Language Model):** Recibe el contexto del RAG y la transcripción para inferir historias en formato Gherkin. +1. **Límite Empresarial Kntro-Soft Enterprise:** Agrupa a nuestros actores principales (Technical Lead y Enterprise Analyst) interactuando centralmente con el **ReqsAI System**. Este es el núcleo de valor donde se procesan las transcripciones, se analizan los requerimientos y se estructuran en historias de usuario. +2. **Proveedores de Inteligencia y Procesamiento (Core Dependencies):** Las dependencias tecnológicas críticas que Reqs-AI delega para cumplir sus objetivos: + * **STT API (Speech-to-Text):** Recibe los fragmentos de audio en tiempo real y devuelve el texto transcrito. + * **Embedding API:** Convierte texto en vectores numéricos para el índice RAG. Es utilizada por **Workspace Management** (vectorización de documentos y glosario) y por **Requirement Discovery** (vectorización de historias de usuario generadas para búsqueda semántica). + * **LLM API (Large Language Model generativo):** Procesa y genera texto de alto nivel. **Workspace Management** la invoca para extraer y estructurar el contenido de documentos subidos por el cliente, produciendo fragmentos de calidad para el RAG. **Requirement Discovery** la invoca para inferir historias de usuario en formato Gherkin a partir de la transcripción y el contexto recuperado del RAG. * **Payment Gateway:** Procesa las transacciones de las suscripciones B2B corporativas. -3. **Herramientas de Ecosistema del Cliente (The Landscape Effect):** La verdadera amplitud del ecosistema se observa en los bordes laterales: - * **Project Management Tool Jira:** El diagrama evidencia que el *Technical Lead* gestiona sus Sprints directamente en Jira. ReqsAI actúa como un puente inteligente que exporta las historias de usuario aprobadas hacia esta herramienta, cerrando la brecha entre el levantamiento de requerimientos y la ejecución ágil. - * **Email Service Provider:** El *Enterprise Analyst* recibe notificaciones de su organización a través de su proveedor de correo, alimentadas por los eventos disparados desde nuestro sistema. +3. **Herramientas de Ecosistema del Cliente (The Landscape Effect):** La verdadera amplitud del ecosistema se evidencia en las relaciones laterales entre los usuarios y las herramientas que ya usan **independientemente** de ReqsAI: + * **Video Conferencing Tool (Google Meet, Zoom, MS Teams):** El *Technical Lead* y el *Enterprise Analyst* realizan sus reuniones de levantamiento de requisitos en estas plataformas. Exportan las grabaciones de audio y las suben manualmente a ReqsAI para su procesamiento. Esta es la fuente upstream del audio que alimenta el pipeline de IA. + * **Project Management Tool (Jira, Trello, Linear):** El *Technical Lead* gestiona sus Sprints directamente en la herramienta de su equipo. ReqsAI exporta las historias aprobadas hacia ella, cerrando la brecha entre el levantamiento de requerimientos y la ejecución ágil. + * **Email Service Provider:** El *Enterprise Analyst* recibe notificaciones e invitaciones de su organización a través de su proveedor de correo corporativo. + * **Customer Support Channel:** El *Technical Lead* y el *Enterprise Analyst* tienen disponible este canal de atención via correo electrónico para resolver dudas, y reportar problemas. -### 4.3.2. Software Architecture Context Level Diagrams +### 4.3.2. Software Architecture Context Level Diagrams Mientras que el Diagrama Landscape nos mostró el panorama del negocio, el **Diagrama de Contexto (Context Level Diagram)** del modelo C4 cambia el foco hacia el interior, centrando toda la atención arquitectónica exclusivamente en el **Reqs-AI System**. Este diagrama responde a la pregunta de "¿Cuáles son las fronteras inmediatas de nuestra solución?". A nivel de contexto, eliminamos las interacciones directas entre los usuarios y los sistemas de terceros (ej. el Technical Lead consultando Jira por su cuenta) y nos limitamos a mapear cómo nuestro sistema es el único orquestador responsable de comunicarse con el mundo exterior para cumplir sus objetivos. -![System Context Diagram](assets/architecture/system-context.png) +![System Context Diagram](assets/diagrams/architecture/system-context.png) **Análisis de Entradas y Salidas del Sistema Central:** @@ -1582,14 +1695,15 @@ A nivel de contexto, eliminamos las interacciones directas entre los usuarios y * **Email Service Provider:** Recibe peticiones vía REST API para enviar los correos transaccionales de recuperación de contraseña y validación de cuentas. * **Sistemas Core (Outbound Tecnológico):** * **STT API (Speech-to-Text):** Recibe el streaming continuo de audio de las reuniones y retorna texto fragmentado con latencia inferior a 2 segundos. - * **LLM API (Inteligencia Generativa):** Recibe un *Prompt* complejo inyectado con el texto de la reunión y el glosario del proyecto, devolviendo un bloque JSON estructurado en Gherkin. + * **Embedding API:** Transforma texto en vectores numéricos para el motor RAG. **Workspace Management** la invoca al ingerir documentos y el glosario técnico del cliente, almacenando los vectores en pgvector. **Requirement Discovery** la invoca para vectorizar las historias de usuario generadas, habilitando búsquedas semánticas sobre el historial del proyecto. + * **LLM API (Inteligencia Generativa):** Genera y procesa lenguaje de alto nivel. **Workspace Management** la invoca para extraer y estructurar el contenido significativo de los documentos subidos por el cliente antes de vectorizarlos, mejorando la calidad de recuperación del RAG. **Requirement Discovery** la invoca para la inferencia generativa: envía un *prompt* que combina la transcripción de la reunión con el contexto recuperado del RAG y recibe como respuesta un bloque JSON estructurado en formato Gherkin. * **Project Management Tool:** Recibe las historias de usuario aprobadas y formateadas para crear automáticamente *Issues* en el backlog del equipo (por ejemplo en Jira). -### 4.3.3. Software Architecture Container Level Diagrams +### 4.3.3. Software Architecture Container Level Diagrams En esta sección presentamos el diagrama de contenedores para el sistema Reqs-AI. Este nivel hace un enfoque al sistema principal para revelar los contenedores de software que lo componen (aplicaciones móviles, web, API, bases de datos), mostrando cómo se distribuyen las responsabilidades, las decisiones tecnológicas de alto nivel y cómo estos componentes se comunican entre sí y con los sistemas externos. -![Container Diagram](assets/architecture/container-diagram.png) +![Container Diagram](assets/diagrams/architecture/container-diagram.png) El sistema Reqs-AI está compuesto por los siguientes contenedores principales: @@ -1598,11 +1712,11 @@ El sistema Reqs-AI está compuesto por los siguientes contenedores principales: * **Mobile App:** Proporciona accesibilidad móvil a los usuarios, permitiéndoles interactuar con el sistema, grabar reuniones o revisar el estado de los requerimientos desde cualquier lugar. Se optó por **Flutter** para asegurar un desarrollo multiplataforma eficiente (iOS y Android) con una base de código unificada. 2. **Distribución y Enrutamiento Perimetral (Edge):** - * **CDN & Reverse Proxy:** Se posiciona como el intermediario absoluto entre las interfaces de usuario (Internet) y la infraestructura interna. Actúa como proxy inverso interceptando todas las peticiones web y móviles. Su función es servir los archivos estáticos de la Web App a alta velocidad desde ubicaciones globales, almacenar respuestas en caché, mitigar ataques (DDoS) y enrutar las peticiones dinámicas (API) hacia el Gateway. En el entorno AWS, la tecnología elegida es **Amazon CloudFront**. - * **API Gateway:** Actúa como la puerta de entrada para todas las peticiones dinámicas (Requests) enrutadas desde el Reverse Proxy. Su responsabilidad es dirigir estas solicitudes hacia los servicios de backend correspondientes, gestionando el *throttling*, métricas de consumo y terminación SSL. + * **CDN & Reverse Proxy (Amazon CloudFront):** Sirve exclusivamente los activos estáticos del SPA Angular desde Edge Locations globales (HTML, CSS, JS), cachea respuestas, mitiga ataques DDoS y enruta el tráfico dinámico de API hacia el API Gateway. **La app móvil no pasa por CloudFront** — es una aplicación nativa instalada desde el App Store/Google Play que llama directamente la API Gateway. + * **API Gateway (AWS API Gateway):** Punto de entrada unificado para todas las peticiones REST y WebSocket, tanto de la app web (enrutadas desde CloudFront) como de la app móvil (conexión directa). Gestiona throttling, métricas de consumo y terminación SSL. 3. **Lógica Core (Backend — Monolito Modular):** - * **Reqs-AI Backend Application:** Desarrollado en **Java 25 con Spring Boot 3**, se despliega como una única unidad de ejecución que concentra toda la inteligencia de negocio del producto. Está estructurado internamente como un **Monolito Modular**: los 5 Bounded Contexts operan como módulos independientes con fronteras de acceso estrictas, comunicándose entre sí mediante interfaces públicas y eventos en memoria —sin tráfico de red interno—, lo que elimina la latencia distribuida y garantiza la coherencia transaccional durante las sesiones de elicitación en tiempo real. Esta decisión prioriza la simplicidad operativa y el *Time-to-Market* en la etapa actual, manteniendo la arquitectura preparada para una migración selectiva a servicios independientes si el volumen futuro lo justifica. + * **Reqs-AI Backend Application:** Desarrollado en **Java 25 con Spring Boot 4**, se despliega como un único contenedor Docker que concentra toda la inteligencia de negocio del producto. Está estructurado internamente como un **Monolito Modular** con Spring Modulith: los 5 Bounded Contexts operan como módulos independientes con fronteras de acceso estrictas, comunicándose entre sí mediante interfaces públicas y eventos en memoria —sin tráfico de red interno—, lo que elimina la latencia distribuida y garantiza la coherencia transaccional. Esta decisión prioriza la simplicidad operativa y el *Time-to-Market* en la etapa actual, manteniendo la arquitectura preparada para una migración selectiva si el volumen futuro lo justifica. | # | Bounded Context | Responsabilidad principal | |---|------------------------|-------------------------------------------------------------------------------------------------------------| @@ -1612,26 +1726,28 @@ El sistema Reqs-AI está compuesto por los siguientes contenedores principales: | 4 | Billing & Subscription | Planes de suscripción, control de cuotas e integración con la pasarela de pagos. | | 5 | Integration Gateway | Exportación de historias aprobadas hacia las herramientas de gestión que el cliente ya utiliza. | -4. **Almacenamiento de Datos:** - * **Database:** Es la base de datos principal de Reqs-AI. Almacena toda la información del dominio. La elección de **PostgreSQL** con la extensión **pgvector** es una decisión estratégica crítica, ya que permite almacenar y consultar *embeddings* vectoriales, facilitando el procesamiento avanzado de IA (RAG) y las búsquedas semánticas. +4. **Almacenamiento de Datos (AWS RDS):** + * **Database:** Base de datos relacional administrada en **AWS RDS** con **PostgreSQL** y la extensión **pgvector**. La elección de pgvector es una decisión estratégica crítica que permite almacenar y consultar *embeddings* vectoriales directamente en la base de datos relacional, habilitando el motor RAG y las búsquedas semánticas sin necesidad de una base de datos vectorial separada. **Comunicación e Integración de Contenedores** -La arquitectura define un flujo de comunicación moderno y orientado a servicios: +La arquitectura define un flujo de comunicación diferenciado según el canal: -* **Comunicación Cliente-Servidor (Internet):** Tanto la aplicación móvil como la web interactúan inicialmente con el **CDN & Reverse Proxy (CloudFront)** a través de **HTTPS**. El proxy inverso sirve la Web App (Angular) y enruta las llamadas de datos hacia el **API Gateway**. Todo el tráfico utiliza conexiones seguras (REST y WebSockets para el streaming de audio). -* **Comunicación Interna:** La API Gateway enruta las llamadas procesadas hacia el *Reqs-AI Backend Application*. Internamente, los Bounded Contexts se comunican mediante eventos en memoria (Domain Events) y persisten su estado de manera síncrona en la base de datos compartida (PostgreSQL). -* **Integración con Sistemas Externos:** En lugar de centralizar todas las salidas, las integraciones están descentralizadas y asignadas al Bounded Context correspondiente que las necesita: - * **IAM** envía credenciales y alertas a través del **Email Service Provider**. - * **Billing & Subscription** procesa transacciones a través del **Payment Gateway**. - * **Requirement Discovery** envía los audios de las reuniones al **STT API** para convertirlos a texto, y delega la inferencia de inteligencia artificial al **LLM API** para la generación de Gherkin. - * **Integration Gateway** exporta finalmente las historias de usuario hacia la herramienta de gestión mediante la **Project Management API** (Jira). +* **Canal Web:** El navegador carga el SPA Angular desde **CloudFront** (activos estáticos cacheados en el Edge). Las llamadas de API del SPA viajan por CloudFront → API Gateway → Backend. +* **Canal Móvil:** La app Flutter (instalada desde App Store/Google Play) realiza llamadas HTTPS directamente la **API Gateway**, sin pasar por CloudFront, ya que no es una aplicación web servida desde un servidor. +* **Comunicación Interna:** La API Gateway enruta todas las peticiones hacia el contenedor del backend. Internamente, los Bounded Contexts se comunican mediante Domain Events en memoria y persisten en la base de datos compartida (AWS RDS PostgreSQL). +* **Integración con Sistemas Externos:** Las integraciones están descentralizadas, asignadas al Bounded Context que las necesita: + * **IAM** envía correos transaccionales vía **Email Service Provider**. + * **Billing & Subscription** procesa pagos vía **Payment Gateway**. + * **Workspace Management** vectoriza documentos y glosario vía **Embedding API**, y procesa el contenido de documentos para el RAG vía **LLM API**. + * **Requirement Discovery** transcribe audio vía **STT API**, vectoriza historias generadas vía **Embedding API** e infiere historias en Gherkin vía **LLM API**. + * **Integration Gateway** exporta historias aprobadas vía **Project Management API** (Jira, Trello, Linear). -### 4.3.4. Software Architecture Deployment Diagrams +### 4.3.4. Software Architecture Deployment Diagrams En esta sección se presenta el diagrama de despliegue, el cual ilustra cómo los contenedores de software de Reqs-AI se mapean a la infraestructura de la nube. Este diagrama detalla los nodos de ejecución, los entornos operativos y la topología de red, priorizando una arquitectura viable, escalable y optimizada en costos. -![Deployment Diagram](assets/architecture/deployment-diagram.png) +![Deployment Diagram](assets/diagrams/architecture/deployment-diagram.png) **Nodos de Despliegue y Distribución de Componentes** @@ -1645,139 +1761,3922 @@ La infraestructura de despliegue se divide en los entornos de cliente, la red de Para garantizar baja latencia y alta seguridad antes de que el tráfico llegue a los servidores principales, se utilizan los nodos Edge de AWS distribuidos globalmente. * **Amazon CloudFront (CDN & Reverse Proxy):** Actúa como el primer punto de contacto (Proxy Inverso). Almacena en caché los archivos estáticos de la Web App en ubicaciones cercanas al usuario para cargas instantáneas, y enruta de forma segura y eficiente el tráfico dinámico hacia la región principal de AWS. -3. **Entorno de Nube - Procesamiento (Server-Side - AWS North America):** - La lógica de negocio se aloja en AWS North America (us-east-1, Virginia), elegida por su alta disponibilidad y ecosistema completo de servicios. - * **AWS API Gateway:** Recibe el tráfico dinámico enrutado desde CloudFront y funciona como el orquestador de las peticiones REST y WebSockets hacia el backend. - * **AWS Elastic Beanstalk:** Es el entorno PaaS (Platform as a Service) encargado de alojar el **Reqs-AI Backend Application**. Elastic Beanstalk abstrae la complejidad de la infraestructura, aprovisionando servidores EC2 subyacentes, autoescalado y monitoreo, permitiendo al equipo enfocarse únicamente en el código del runtime de Java. +3. **Entorno de Nube - Procesamiento (AWS North America — us-east-1, Virginia):** + La lógica de negocio se aloja en la región de AWS North America, elegida por su alta disponibilidad y ecosistema completo de servicios administrados. + * **AWS API Gateway:** Recibe el tráfico dinámico desde CloudFront (web) y directamente desde la app móvil, funcionando como orquestador de peticiones REST y WebSocket hacia el backend. + * **ECS Cluster (AWS ECS + Fargate):** El backend se despliega como un contenedor Docker en **AWS ECS con Fargate** (serverless containers). Fargate abstrae completamente la gestión de servidores EC2 subyacentes, provisionando cómputo bajo demanda con autoescalado automático. La task definition del ECS define dos contenedores en la misma unidad de ejecución: el **ReqsAI Backend Service** (Java 25 + Spring Boot 4) y el **Grafana Alloy** como sidecar de observabilidad. + * **Observability Server (AWS EC2 + Docker Compose):** Una instancia EC2 dedicada ejecuta el stack de observabilidad completo mediante Docker Compose: **Prometheus** (almacén de métricas, consultado con PromQL), **Loki** (agregación de logs, consultado con LogQL), **Tempo** (trazas distribuidas, consultado con TraceQL) y **Grafana** (dashboard unificado que visualiza las tres fuentes). Grafana Alloy, corriendo como sidecar en el ECS Cluster, colecta las métricas del endpoint `/actuator/prometheus`, logs del stdout del contenedor y trazas OTLP, enviándolos al servidor de observabilidad mediante push. -4. **Entorno de Nube - Persistencia (Database as a Service):** - * **Supabase Cloud (PostgreSQL):** Se delegó el almacenamiento a Supabase, una plataforma BaaS (Backend as a Service). Esta decisión permite aprovechar una base de datos PostgreSQL robusta, gestionada y con la extensión **pgvector** nativa (esencial para los *embeddings* y RAG de los requerimientos), reduciendo drásticamente la carga operativa y los costos. +4. **Entorno de Nube - Persistencia (AWS RDS):** + * **AWS RDS (PostgreSQL + pgvector):** La base de datos principal se gestiona completamente en **Amazon RDS**, el servicio de base de datos relacional administrado de AWS. Se utiliza **PostgreSQL** con la extensión **pgvector** habilitada, esencial para el almacenamiento de embeddings vectoriales que alimentan el motor RAG. RDS provee backups automáticos, failover multi-AZ y actualizaciones de parches sin downtime. **Comunicación e Interacción de Nodos** -* Las aplicaciones (Mobile y Web) se comunican vía internet mediante **HTTPS** con el **Amazon CloudFront** ubicado en el *Edge Location* más cercano. -* CloudFront sirve los recursos estáticos web directamente y enruta las solicitudes API hacia el **AWS API Gateway** en la región de AWS North America. -* La API Gateway enruta el tráfico internamente hacia el entorno de **AWS Elastic Beanstalk**, donde reside la lógica del Monolito Modular. -* El backend de Spring Boot se conecta de manera externa y segura hacia el clúster gestionado en **Supabase Cloud** para realizar operaciones transaccionales (*Reads and writes*) sobre la base de datos compartida. +* **App Web:** El navegador carga el SPA Angular desde **CloudFront** (Edge Location más cercano). Las llamadas de API del SPA viajan CloudFront → API Gateway → ECS Backend. +* **App Móvil:** La app Flutter instalada en el dispositivo del usuario realiza llamadas HTTPS **directamente la API Gateway**, sin pasar por CloudFront, ya que no es una aplicación web servida desde CDN. +* **Observabilidad:** Grafana Alloy (sidecar en ECS) colecta continuamente métricas, logs y trazas del backend y los envía al Observability Server en EC2. Grafana consulta Prometheus, Loki y Tempo para mostrar el estado del sistema en tiempo real. +* **Persistencia:** El backend se conecta a **AWS RDS** via JDBC/JPA para todas las operaciones transaccionales de los 5 Bounded Contexts. # Capítulo V: Tactical-Level Software Design -## 5.X. Bounded Context: +## 5.1. Bounded Context: IAM -### 5.X.1. Domain Layer +El BC IAM gestiona la identidad, autenticación y sesiones de los usuarios de Reqs-AI. Es responsable desde el registro de cuenta hasta la emisión y rotación de tokens de acceso, verificación de correo electrónico, actualización de perfil y almacenamiento de preferencias de navegación del usuario. No administra roles ni permisos por organización; esa responsabilidad pertenece al BC Workspace Management. -### 5.X.2. Interface Layer +### 5.1.1. Domain Layer -### 5.X.3. Application Layer +Esta capa contiene el núcleo del negocio del BC IAM: reglas de autenticación, ciclo de vida de cuentas y gestión de sesiones mediante tokens. No depende de ningún framework externo a nivel de lógica. -### 5.X.4. Infrastructure Layer +**Aggregate Roots** -### 5.X.6. Bounded Context Software Architecture Component Level Diagrams +**Aggregate: `Account`** -### 5.X.7. Bounded Context Software Architecture Code Level Diagrams +| Campo | Detalle | +|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.domain.model.aggregates` | +| **Extiende** | `AbstractAggregateRoot` | +| **Propósito** | Representa las credenciales y el estado del ciclo de vida de una cuenta. Controla las invariantes de autenticación: una cuenta no puede autenticarse si no está verificada o está suspendida. | +| **Anotaciones JPA** | `@Entity`, `@Table(name = "accounts")` | -#### 5.X.7.1. Bounded Context Domain Layer Class Diagrams +**Atributos:** -#### 5.X.7.2. Bounded Context Database Design Diagram +| Atributo | Tipo | Columna JPA | Descripción | +|-----------------------------|-----------------|--------------------------------|----------------------------------------------------------------| +| `id` | `AccountId` | `id` | Identificador único de la cuenta (UUID). | +| `email` | `Email` | `@Embedded` | Correo electrónico único de acceso, normalizado a minúsculas. | +| `passwordHash` | `String` | `password_hash` | Hash BCrypt de la contraseña. | +| `status` | `AccountStatus` | `status` | Estado actual de la cuenta en su ciclo de vida. | +| `verificationCode` | `String?` | `verification_code` | Código OTP de verificación de correo. Nulo una vez verificado. | +| `verificationCodeExpiresAt` | `Instant?` | `verification_code_expires_at` | Fecha de expiración del OTP. | -# Capítulo VI: Solution UX Design +**Constructores:** -## 6.1. Style Guidelines +| Constructor | Visibilidad | Propósito | +|---------------------------------------------|-------------|---------------------------------------------------------------| +| `protected Account()` | `protected` | Para JPA. No instanciar directamente. | +| `Account(Email email, String passwordHash)` | `public` | Crea la cuenta en estado `PENDING_VERIFICATION`. Genera UUID. | -### 6.1.1. General Style Guidelines +**Métodos de negocio:** -### 6.1.2. Web, Mobile & Devices Style Guidelines +| Método | Visibilidad | Parámetros | Retorna | Descripción | Excepciones lanzadas | +|------------------------------------------------------------|-------------|--------------------------------------|---------|----------------------------------------|-------------------------------------------------------------------------| +| `verifyEmail(String code, Instant now)` | `public` | `code: String`, `now: Instant` | `void` | Valida el OTP y activa la cuenta. | `InvalidVerificationCodeException` si el código es incorrecto o expiró. | +| `changePassword(String newPasswordHash)` | `public` | `newPasswordHash: String` | `void` | Reemplaza el hash de contraseña. | — | +| `suspend()` | `public` | — | `void` | Transición a estado `SUSPENDED`. | `CannotSuspendAccountException` si ya está suspendida o eliminada. | +| `activate()` | `public` | — | `void` | Transición a estado `ACTIVE`. | — | +| `delete()` | `public` | — | `void` | Baja lógica: estado `DELETED`. | — | +| `generateVerificationCode(String code, Instant expiresAt)` | `public` | `code: String`, `expiresAt: Instant` | `void` | Almacena nuevo OTP (usado en reenvío). | — | -## 6.2. Information Architecture +**Métodos de consulta:** -### 6.2.2. Labeling Systems +| Método | Visibilidad | Retorna | Descripción | +|---------------------------|-------------|-----------|------------------------------------------------| +| `isPendingVerification()` | `public` | `boolean` | `true` si el status es `PENDING_VERIFICATION`. | +| `isActive()` | `public` | `boolean` | `true` si el status es `ACTIVE`. | +| `isSuspended()` | `public` | `boolean` | `true` si el status es `SUSPENDED`. | +| `isDeleted()` | `public` | `boolean` | `true` si el status es `DELETED`. | -### 6.2.3. Searching Systems +**Relaciones:** -### 6.2.4. SEO Tags and Meta Tags +| Relación | Tipo | Multiplicidad | Detalles | +|----------------------------|-------------------|---------------|------------------------------------------------------------------| +| `Account` → `User` | Referencia por ID | 1..1 | `User` mantiene `accountId: AccountId`. Frontera de aggregate. | +| `Account` → `RefreshToken` | Referencia por ID | 1..* | `RefreshToken` mantiene `userId: UserId`. Frontera de aggregate. | -### 6.2.5. Navigation Systems +--- -## 6.3. Landing Page UI Design +**Aggregate: `User`** -### 6.3.1. Landing Page Wireframe +| Campo | Detalle | +|---------------------|-------------------------------------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.domain.model.aggregates` | +| **Extiende** | `AbstractAggregateRoot` | +| **Propósito** | Representa el perfil del usuario vinculado a una cuenta. Controla actualizaciones de datos personales y preferencias de navegación. | +| **Anotaciones JPA** | `@Entity`, `@Table(name = "users")` | -### 6.3.2. Landing Page Mock-up +**Atributos:** -## 6.4. Applications UX/UI Design +| Atributo | Tipo | Columna JPA | Descripción | +|---------------|-------------------|--------------|-----------------------------------------| +| `id` | `UserId` | `id` | Identificador único del usuario (UUID). | +| `accountId` | `AccountId` | `account_id` | Referencia a la cuenta asociada. | +| `firstName` | `String` | `first_name` | Nombres del usuario. | +| `lastName` | `String` | `last_name` | Apellidos del usuario. | +| `avatarUrl` | `String?` | `avatar_url` | URL de foto de perfil (opcional). | +| `preferences` | `UserPreferences` | `@Embedded` | Preferencias de navegación del usuario. | -### 6.4.1. Applications Wireframes +**Constructores:** -### 6.4.2. Applications Wireflow Diagrams +| Constructor | Visibilidad | Propósito | +|----------------------------------------------------------------|-------------|--------------------------------------------------------------------------------| +| `protected User()` | `protected` | Para JPA. No instanciar directamente. | +| `User(AccountId accountId, String firstName, String lastName)` | `public` | Crea perfil asociado a una cuenta. Inicializa `preferences` con valores nulos. | -### 6.4.2. Applications Mock-ups +**Métodos de negocio:** -### 6.4.3. Applications User Flow Diagrams +| Método | Visibilidad | Parámetros | Retorna | Descripción | Excepciones lanzadas | +|----------------------------------------------------------------------|-------------|---------------------------------------------------------------|---------|-------------------------------------------|----------------------| +| `updateProfile(String firstName, String lastName, String avatarUrl)` | `public` | `firstName: String`, `lastName: String`, `avatarUrl: String?` | `void` | Actualiza nombre y foto de perfil. | — | +| `updatePreferences(UserPreferences preferences)` | `public` | `preferences: UserPreferences` | `void` | Reemplaza las preferencias de navegación. | — | -## 6.5. Applications Prototyping +**Métodos de consulta:** -# Capítulo VII: Product Implementation, Validation & Deployment +| Método | Visibilidad | Retorna | Descripción | +|-----------------|-------------|----------|---------------------------------------| +| `getFullName()` | `public` | `String` | Retorna `firstName + " " + lastName`. | -## 7.1. Software Configuration Management +**Relaciones:** -### 7.1.1. Software Development Environment Configuration +| Relación | Tipo | Multiplicidad | Detalles | +|----------------------------|---------------------------|---------------|---------------------------------------------------------------------| +| `User` → `AccountId` | Referencia por ID | 1..1 | Mantiene frontera de aggregate. Nunca el objeto `Account` completo. | +| `User` → `UserPreferences` | Composición (`@Embedded`) | 1..1 | VO embebido, siempre presente. | -### 7.1.2. Source Code Management +--- -### 7.1.3. Source Code Style Guide & Conventions +**Aggregate: `RefreshToken`** -### 7.1.4. Software Deployment Configuration +| Campo | Detalle | +|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.domain.model.aggregates` | +| **Extiende** | `AbstractAggregateRoot` | +| **Propósito** | Representa un token de renovación de sesión. Controla su ciclo de vida (emisión, rotación, revocación) y garantiza que el token solo pueda usarse si está activo y no expirado. | +| **Anotaciones JPA** | `@Entity`, `@Table(name = "refresh_tokens")` | -## 7.2. Solution Implementation +**Atributos:** -### 7.2.X. Sprint n +| Atributo | Tipo | Columna JPA | Descripción | +|-------------|------------------|--------------|-------------------------------------------------| +| `id` | `RefreshTokenId` | `id` | Identificador único del token (UUID). | +| `tokenHash` | `String` | `token_hash` | Hash SHA-256 del token en texto plano. | +| `userId` | `UserId` | `user_id` | Usuario propietario del token. | +| `status` | `TokenStatus` | `status` | Estado actual del token. | +| `expiresAt` | `Instant` | `expires_at` | Fecha de expiración. | +| `revokedAt` | `Instant?` | `revoked_at` | Fecha de revocación explícita (nulo si activo). | -#### 7.2.X.1. Sprint Planning n +**Constructores:** -#### 7.2.X.2. Sprint Backlog n +| Constructor | Visibilidad | Propósito | +|--------------------------------------------------------------------|-------------|------------------------------------------| +| `protected RefreshToken()` | `protected` | Para JPA. No instanciar directamente. | +| `RefreshToken(String tokenHash, UserId userId, Instant expiresAt)` | `public` | Emite un nuevo token en estado `ACTIVE`. | -#### 7.2.X.3. Development Evidence for Sprint Review +**Métodos de negocio:** -#### 7.2.X.4. Testing Suite Evidence for Sprint Review +| Método | Visibilidad | Parámetros | Retorna | Descripción | Excepciones lanzadas | +|-----------------------------|-------------|----------------------|---------|-----------------------------------------------|----------------------------------------------------------------| +| `revoke(Instant revokedAt)` | `public` | `revokedAt: Instant` | `void` | Marca el token como `REVOKED`. | `InvalidRefreshTokenException` si ya está revocado o expirado. | +| `rotate()` | `public` | — | `void` | Revoca el token actual para emitir uno nuevo. | `InvalidRefreshTokenException` si no está activo. | -#### 7.2.X.5. Execution Evidence for Sprint Review +**Métodos de consulta:** -#### 7.2.X.6. Services Documentation Evidence for Sprint Review +| Método | Visibilidad | Retorna | Descripción | +|--------------------------|-------------|-----------|---------------------------------------------------| +| `isValid(Instant now)` | `public` | `boolean` | `true` si el status es `ACTIVE` y no ha expirado. | +| `isExpired(Instant now)` | `public` | `boolean` | `true` si `expiresAt` es anterior a `now`. | -#### 7.2.X.7. Software Deployment Evidence for Sprint Review +**Relaciones:** -#### 7.2.X.8. Team Collaboration Insights during Sprint +| Relación | Tipo | Multiplicidad | Detalles | +|---------------------------|-------------------|---------------|---------------------------------| +| `RefreshToken` → `UserId` | Referencia por ID | 1..1 | Mantiene frontera de aggregate. | -## 7.3. Validation Interviews +--- -### 7.3.1. Diseño de Entrevistas +**Value Objects** -### 7.3.2. Registro de Entrevistas +**Value Object: `Email`** -### 7.3.3. Evaluaciones según heurísticas +| Campo | Detalle | +|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.domain.model.valueobjects` | +| **Tipo Java** | `record` | +| **Propósito** | Encapsula una dirección de correo electrónico con formato válido y normalización a minúsculas. Garantiza que cualquier instancia es siempre un email válido. | -## 7.4. Video About-the-Product +**Campos:** -# Conclusiones +| Campo | Tipo | Descripción | +|---------|----------|----------------------------------| +| `value` | `String` | Correo normalizado a minúsculas. | -El equipo concluye que el problema abordado es real, recurrente y de alto impacto en el ciclo de vida del software: la ambigüedad en el levantamiento de requisitos y la sobrecarga de postprocesamiento generan retrabajo, retrasos y riesgo de construir funcionalidades incorrectas. La evidencia obtenida en entrevistas confirma un patrón consistente en ambos segmentos objetivo (Líder Técnico de Startup y Analista de Sistemas/Producto): transformar conversaciones en requisitos claros, trazables y accionables sigue siendo el principal cuello de botella. +**Validaciones en compact constructor:** -Sobre esta base, Reqs-AI se consolida como una propuesta de valor pertinente al combinar asistencia en tiempo real, generación estructurada de historias de usuario y criterios de aceptación, y mecanismos de integración con herramientas de gestión del backlog. El enfoque del producto no reemplaza el criterio profesional del analista o líder técnico, sino que lo potencia para reducir omisiones, acelerar la claridad funcional y mejorar la calidad de entrada hacia desarrollo y QA. +| Regla | Excepción lanzada | Error Code | +|-----------------------------------------------|-------------------------|-----------------| +| No puede ser nulo o vacío | `InvalidValueException` | `INVALID_EMAIL` | +| Debe tener formato de email válido (RFC 5322) | `InvalidValueException` | `INVALID_EMAIL` | -Asimismo, el trabajo desarrollado en el informe demuestra coherencia metodológica entre descubrimiento, análisis y diseño de solución. Los artefactos de Lean UX, entrevistas, need finding, user stories, backlog e impact mapping se enlazan con decisiones arquitectónicas estratégicas (DDD, EventStorming, Bounded Contexts y lineamientos de seguridad multitenancy con RLS), aportando trazabilidad desde la necesidad del usuario hasta la estructura técnica propuesta. +**Factory method:** -Respecto a las hipótesis planteadas, el equipo considera que cuentan con validación inicial de problema y de deseabilidad, debido a la convergencia de hallazgos cualitativos y cuantitativos en las entrevistas. Sin embargo, su validación de desempeño y negocio permanece parcial, ya que métricas objetivas como reducción de reuniones de aclaración, tiempo de edición manual por sesión, retención de uso y sincronización efectiva al backlog deben medirse con el producto en operación real. +| Método | Firma | Descripción | +|--------|---------------------------------|---------------------------------------------------------| +| `of` | `static Email of(String value)` | Normaliza a minúsculas y llama al constructor compacto. | -La principal limitación actual del proyecto es que aún no se presenta evidencia completa de implementación, pruebas de campo y resultados longitudinales de adopción. En consecuencia, aunque la arquitectura y el diseño funcional están sólidamente fundamentados, todavía es necesario contrastar el comportamiento del sistema en escenarios productivos con usuarios reales y condiciones de carga, seguridad y dependencia de servicios externos de IA. +--- -Como siguientes pasos, se recomienda priorizar un MVP enfocado en el flujo crítico end-to-end (captura de reunión, síntesis guiada, generación de historias con criterios de aceptación y exportación a backlog), ejecutar pilotos controlados en startups y entornos enterprise, y definir un tablero de métricas para validar hipótesis de valor, eficiencia y confianza. Con ello, Reqs-AI podrá transitar de una solución bien diseñada en el plano estratégico a una plataforma validada en impacto operativo y escalabilidad de negocio. +**Value Object: `UserPreferences`** -# Bibliografía +| Campo | Detalle | +|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.domain.model.valueobjects` | +| **Tipo Java** | `record` | +| **Propósito** | Almacena las preferencias de navegación del usuario. Persiste el último contexto de trabajo para restaurar la sesión al iniciar la aplicación. Respaldado por US13 (seleccionar organización activa). | ->Pulse of the Profession (2018) Success in Disruptive Times. Project Management Institute. Recuperado el 15 de Abril de 2025, de https://www.pmi.org/learning/thought-leadership/pulse/pulse-of-the-profession-2018 +**Campos:** ->Jhonson J (2020) CHAOS Report: Beyond Infinity. Standish Group. Recuperado el 15 de Abril de 2025, de https://www.standishgroup.com/products/copy-of-chaos-report-beyond-infinity-digital-version +| Campo | Tipo | Descripción | +|------------------------|-----------|-----------------------------------------------------------------------------------| +| `lastVisitedOrgId` | `String?` | ID de la última organización visitada. Nulo si el usuario no ha visitado ninguna. | +| `lastVisitedProjectId` | `String?` | ID del último proyecto visitado. Nulo si el usuario no ha visitado ninguno. | + +**Validaciones en compact constructor:** + +| Regla | Excepción lanzada | Error Code | +|----------------------------------------------------------------|-------------------------|-----------------------| +| Si se provee `lastVisitedOrgId`, no puede ser cadena vacía | `InvalidValueException` | `INVALID_PREFERENCES` | +| Si se provee `lastVisitedProjectId`, no puede ser cadena vacía | `InvalidValueException` | `INVALID_PREFERENCES` | + +--- + +**Value Object: `AccountStatus`** + +| Campo | Detalle | +|---------------|--------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.domain.model.valueobjects` | +| **Tipo Java** | `enum` | +| **Propósito** | Define los estados posibles del ciclo de vida de una cuenta. | + +**Valores:** + +| Valor | Descripción en el negocio | +|------------------------|-----------------------------------------------------------------------------------| +| `PENDING_VERIFICATION` | La cuenta fue creada pero el correo no ha sido verificado. No puede autenticarse. | +| `ACTIVE` | La cuenta está activa y puede operar normalmente. | +| `SUSPENDED` | La cuenta fue suspendida administrativamente. No puede autenticarse. | +| `DELETED` | Baja lógica de la cuenta. No puede recuperarse mediante flujos estándar. | + +--- + +**Value Object: `TokenStatus`** + +| Campo | Detalle | +|---------------|--------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.domain.model.valueobjects` | +| **Tipo Java** | `enum` | +| **Propósito** | Define los estados posibles del ciclo de vida de un refresh token. | + +**Valores:** + +| Valor | Descripción en el negocio | +|-----------|------------------------------------------------------| +| `ACTIVE` | Token vigente, puede usarse para renovar la sesión. | +| `REVOKED` | Token revocado explícitamente (sign-out o rotación). | +| `EXPIRED` | Token expirado por vencimiento de tiempo. | + +--- + +**Domain Exceptions** + +| Clase | Extiende | HTTP Status | Error Code | Cuándo se lanza | +|------------------------------------|----------------------------------|-------------|-----------------------------|----------------------------------------------------------| +| `AccountNotFoundException` | `EntityNotFoundException` | 404 | `ACCOUNT_NOT_FOUND` | No se encuentra la cuenta por ID o email. | +| `AccountAlreadyExistsException` | `BusinessRuleViolationException` | 409 | `ACCOUNT_ALREADY_EXISTS` | Intento de registrar un email ya existente. | +| `InvalidCredentialsException` | `AuthenticationException` | 401 | `INVALID_CREDENTIALS` | Email o contraseña incorrectos en sign-in. | +| `AccountNotVerifiedException` | `BusinessRuleViolationException` | 409 | `ACCOUNT_NOT_VERIFIED` | Intento de autenticarse sin haber verificado el correo. | +| `CannotSuspendAccountException` | `BusinessRuleViolationException` | 409 | `CANNOT_SUSPEND_ACCOUNT` | La cuenta ya está suspendida o eliminada. | +| `InvalidRefreshTokenException` | `AuthenticationException` | 401 | `INVALID_REFRESH_TOKEN` | El refresh token no existe, fue revocado o ya expiró. | +| `InvalidVerificationCodeException` | `BusinessRuleViolationException` | 409 | `INVALID_VERIFICATION_CODE` | El OTP es incorrecto o ha expirado. | +| `UserNotFoundException` | `EntityNotFoundException` | 404 | `USER_NOT_FOUND` | No se encuentra el perfil de usuario por ID o accountId. | + +--- + +**Domain Events** + +| Clase | Paquete | Campos clave | Se publica cuando | Consumido por | +|-----------------------------------|----------------------------|----------------------------------------------------------------|--------------------------------------------------|-----------------------------------------------------------| +| `AccountCreatedEvent` | `iam/api/` | `accountId`, `userId`, `email`, `occurredAt` | Se completa `SignUpCommandHandler` exitosamente. | Interno. | +| `EmailVerificationRequestedEvent` | `iam/api/` | `email`, `verificationCode`, `expirationMinutes`, `occurredAt` | Se crea una cuenta nueva o se reenvía el OTP. | `EmailVerificationRequestedEventListener` (envía correo). | +| `AccountVerifiedEvent` | `iam/domain/model/events/` | `accountId`, `occurredAt` | La cuenta transiciona a estado `ACTIVE`. | Interno. | + +--- + +**Commands** + +| Clase | Paquete | Campos | Handler que lo procesa | +|---------------------------------|--------------------------|---------------------------------------------------------------------------------|----------------------------------------| +| `SignUpCommand` | `domain/model/commands/` | `email: String`, `password: String`, `firstName: String`, `lastName: String` | `SignUpCommandHandler` | +| `SignInCommand` | `domain/model/commands/` | `email: String`, `password: String` | `SignInCommandHandler` | +| `VerifyEmailCommand` | `domain/model/commands/` | `email: String`, `code: String` | `VerifyEmailCommandHandler` | +| `ResendVerificationCodeCommand` | `domain/model/commands/` | `email: String` | `ResendVerificationCodeCommandHandler` | +| `ChangePasswordCommand` | `domain/model/commands/` | `userId: String`, `currentPassword: String`, `newPassword: String` | `ChangePasswordCommandHandler` | +| `RefreshSessionCommand` | `domain/model/commands/` | `refreshToken: String` | `RefreshSessionCommandHandler` | +| `RevokeRefreshTokenCommand` | `domain/model/commands/` | `refreshToken: String` | `RevokeRefreshTokenCommandHandler` | +| `UpdateUserProfileCommand` | `domain/model/commands/` | `userId: String`, `firstName: String`, `lastName: String`, `avatarUrl: String?` | `UpdateUserProfileCommandHandler` | +| `UpdateUserPreferencesCommand` | `domain/model/commands/` | `userId: String`, `lastVisitedOrgId: String?`, `lastVisitedProjectId: String?` | `UpdateUserPreferencesCommandHandler` | + +**Queries** + +| Clase | Paquete | Campos | Handler que lo procesa | +|-----------------------------|-------------------------|---------------------|------------------------------------| +| `GetAuthenticatedUserQuery` | `domain/model/queries/` | — | `GetAuthenticatedUserQueryHandler` | +| `GetUserByIdQuery` | `domain/model/queries/` | `userId: String` | `GetUserByIdQueryHandler` | +| `GetUserByAccountIdQuery` | `domain/model/queries/` | `accountId: String` | `GetUserByAccountIdQueryHandler` | + +--- + +### 5.1.2. Interface Layer + +Esta capa es la puerta de entrada HTTP al BC IAM. Expone los endpoints de autenticación y gestión de perfil siguiendo el patrón de separación entre interfaz Swagger e implementación. + +**Controllers** + +**`AuthenticationController` (Swagger Interface)** + +| Campo | Detalle | +|-----------------|----------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.interfaces.rest.swagger` | +| **Base path** | `ApiVersioning.BASE + "/authentication"` → `/api/v1/authentication` | +| **Tag OpenAPI** | `"Authentication"` | +| **Propósito** | Contrato OpenAPI para registro, autenticación y gestión de sesiones. Sin lógica. | + +| Método HTTP | Path | Nombre del método | Request DTO | Response DTO | Códigos HTTP | +|-------------|----------------|--------------------------|---------------------------------|-----------------------------|--------------------| +| `POST` | `/sign-up` | `signUp` | `SignUpRequest` | `AuthenticatedUserResponse` | 201, 400, 409 | +| `POST` | `/sign-in` | `signIn` | `SignInRequest` | `AuthenticatedUserResponse` | 200, 400, 401, 409 | +| `POST` | `/verify` | `verifyEmail` | `VerifyEmailRequest` | `void` | 200, 400, 409 | +| `POST` | `/resend-code` | `resendVerificationCode` | `ResendVerificationCodeRequest` | `void` | 200, 400, 404 | +| `POST` | `/refresh` | `refreshSession` | `RefreshSessionRequest` | `AuthenticatedUserResponse` | 200, 401 | +| `POST` | `/sign-out` | `signOut` | `RevokeRefreshTokenRequest` | `void` | 204, 401 | + +**`AuthenticationControllerImpl` (Implementation)** + +| Campo | Detalle | +|-----------------|---------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.interfaces.rest.controllers` | +| **Anotaciones** | `@Slf4j`, `@RestController`, `@RequiredArgsConstructor` | +| **Implementa** | `AuthenticationController` | + +| Handler | Para qué endpoint | +|----------------------------------------|---------------------| +| `SignUpCommandHandler` | `POST /sign-up` | +| `SignInCommandHandler` | `POST /sign-in` | +| `VerifyEmailCommandHandler` | `POST /verify` | +| `ResendVerificationCodeCommandHandler` | `POST /resend-code` | +| `RefreshSessionCommandHandler` | `POST /refresh` | +| `RevokeRefreshTokenCommandHandler` | `POST /sign-out` | + +--- + +**`UserController` (Swagger Interface)** + +| Campo | Detalle | +|-----------------|------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.interfaces.rest.swagger` | +| **Base path** | `ApiVersioning.BASE + "/users"` → `/api/v1/users` | +| **Tag OpenAPI** | `"Users"` | +| **Propósito** | Contrato OpenAPI para consulta y actualización del perfil del usuario autenticado. | + +| Método HTTP | Path | Nombre del método | Request DTO | Response DTO | Códigos HTTP | +|-------------|-------------------|------------------------|----------------------------|----------------|---------------| +| `GET` | `/me` | `getAuthenticatedUser` | — | `UserResponse` | 200, 401 | +| `PATCH` | `/me/profile` | `updateProfile` | `UpdateProfileRequest` | `UserResponse` | 200, 400, 401 | +| `PATCH` | `/me/preferences` | `updatePreferences` | `UpdatePreferencesRequest` | `UserResponse` | 200, 400, 401 | + +**`UserControllerImpl` (Implementation)** + +| Campo | Detalle | +|-----------------|---------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.interfaces.rest.controllers` | +| **Anotaciones** | `@Slf4j`, `@RestController`, `@RequiredArgsConstructor` | +| **Implementa** | `UserController` | + +| Handler | Para qué endpoint | +|---------------------------------------|-------------------------| +| `GetAuthenticatedUserQueryHandler` | `GET /me` | +| `UpdateUserProfileCommandHandler` | `PATCH /me/profile` | +| `UpdateUserPreferencesCommandHandler` | `PATCH /me/preferences` | + +--- + +**Request DTOs** + +| Clase | Paquete | Campos | Validaciones Jakarta | +|---------------------------------|--------------------------------|------------------------------------------------------------------------------|---------------------------------------------------------------------| +| `SignUpRequest` | `interfaces/rest/dto/request/` | `email: String`, `password: String`, `firstName: String`, `lastName: String` | `@NotBlank` en todos, `@Email` en email, `@Size(min=8)` en password | +| `SignInRequest` | `interfaces/rest/dto/request/` | `email: String`, `password: String` | `@NotBlank` en todos | +| `VerifyEmailRequest` | `interfaces/rest/dto/request/` | `email: String`, `code: String` | `@NotBlank` en todos | +| `ResendVerificationCodeRequest` | `interfaces/rest/dto/request/` | `email: String` | `@NotBlank`, `@Email` | +| `RefreshSessionRequest` | `interfaces/rest/dto/request/` | `refreshToken: String` | `@NotBlank` | +| `RevokeRefreshTokenRequest` | `interfaces/rest/dto/request/` | `refreshToken: String` | `@NotBlank` | +| `UpdateProfileRequest` | `interfaces/rest/dto/request/` | `firstName: String`, `lastName: String`, `avatarUrl: String?` | `@NotBlank` en `firstName` y `lastName` | +| `UpdatePreferencesRequest` | `interfaces/rest/dto/request/` | `lastVisitedOrgId: String?`, `lastVisitedProjectId: String?` | Opcionales, sin `@NotBlank` | + +--- + +**Response DTOs** + +| Clase | Paquete | Campos | Notas | +|-----------------------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------| +| `AuthenticatedUserResponse` | `interfaces/rest/dto/response/` | `id: String`, `email: String`, `firstName: String`, `lastName: String`, `accessToken: String`, `refreshToken: String` | `@Builder` + `@Schema` en cada campo | +| `UserResponse` | `interfaces/rest/dto/response/` | `id: String`, `email: String`, `firstName: String`, `lastName: String`, `avatarUrl: String?`, `lastVisitedOrgId: String?`, `lastVisitedProjectId: String?` | `@Builder` + `@Schema` en cada campo | + +--- + +**Mappers** + +**Request Mappers:** + +| Clase | Método | Convierte | +|----------------------------------|--------------------------------------------------------------------------------------------------|------------------------------------------------------| +| `SignUpRequestMapper` | `static SignUpCommand toCommand(SignUpRequest request)` | DTO de registro → Command. | +| `SignInRequestMapper` | `static SignInCommand toCommand(SignInRequest request)` | DTO de autenticación → Command. | +| `UpdateProfileRequestMapper` | `static UpdateUserProfileCommand toCommand(String userId, UpdateProfileRequest request)` | Combina userId del contexto de seguridad + body. | +| `UpdatePreferencesRequestMapper` | `static UpdateUserPreferencesCommand toCommand(String userId, UpdatePreferencesRequest request)` | Combina userId del contexto + preferencias del body. | + +**Response Mappers:** + +| Clase | Método | Convierte | +|-----------------------------------|---------------------------------------------------------------------------------------------------|------------------------------------------| +| `AuthenticatedUserResponseMapper` | `static AuthenticatedUserResponse toResponse(User user, String accessToken, String refreshToken)` | User + tokens → DTO de autenticación. | +| `UserResponseMapper` | `static UserResponse toResponse(User user, String email)` | User + email de Account → DTO de perfil. | + +--- + +### 5.1.3. Application Layer + +Esta capa orquesta los casos de uso del BC IAM. No contiene lógica de negocio; conecta la capa de interfaces con el dominio a través de puertos. + +**Command Handlers** + +**`SignUpCommandHandler`** + +| Campo | Detalle | +|------------------------|-------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.application.authentication.signup` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `SignUpCommand` | +| **Retorna** | `AuthenticatedUserResponse` | +| **Propósito** | Registra una nueva cuenta y perfil de usuario, genera OTP y publica evento de verificación de correo. | + +**Dependencias:** + +| Puerto | Para qué se usa | +|-----------------------------|----------------------------------------------------| +| `AccountRepositoryPort` | Verificar unicidad de email y persistir `Account`. | +| `UserRepositoryPort` | Persistir `User`. | +| `HashingServicePort` | Hashear la contraseña. | +| `VerificationServicePort` | Generar OTP y tiempo de expiración. | +| `ApplicationEventPublisher` | Publicar `EmailVerificationRequestedEvent`. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|---------------------------------------------------------------------------------------------------------|---------------------------------| +| 1 | Verificar que no exista una cuenta con el mismo email. | `AccountAlreadyExistsException` | +| 2 | Hashear la contraseña con `HashingServicePort.encode()`. | — | +| 3 | Crear `Account` con `new Account(Email.of(email), passwordHash)`. | — | +| 4 | Generar OTP con `VerificationServicePort` y llamar `account.generateVerificationCode(code, expiresAt)`. | — | +| 5 | Persistir `Account` con `accountRepository.save(account)`. | — | +| 6 | Crear `User` con `new User(account.getId(), firstName, lastName)`. | — | +| 7 | Persistir `User` con `userRepository.save(user)`. | — | +| 8 | Publicar `EmailVerificationRequestedEvent(email, code, expirationMinutes)`. | — | + +--- + +**`SignInCommandHandler`** + +| Campo | Detalle | +|------------------------|--------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.application.authentication.signin` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `SignInCommand` | +| **Retorna** | `AuthenticatedUserResponse` | +| **Propósito** | Valida credenciales, emite access token JWT y persiste un nuevo refresh token. | + +**Dependencias:** + +| Puerto | Para qué se usa | +|------------------------------|------------------------------------------| +| `AccountRepositoryPort` | Cargar `Account` por email. | +| `UserRepositoryPort` | Cargar `User` por accountId. | +| `HashingServicePort` | Comparar contraseña con hash almacenado. | +| `TokenServicePort` | Emitir JWT (access token). | +| `RefreshTokenRepositoryPort` | Persistir el nuevo `RefreshToken`. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|----------------------------------------------------------|--------------------------------------------------------------| +| 1 | Cargar `Account` por email. | `AccountNotFoundException` | +| 2 | Verificar que la cuenta esté `ACTIVE`. | `AccountNotVerifiedException`, `InvalidCredentialsException` | +| 3 | Comparar contraseña con `HashingServicePort.matches()`. | `InvalidCredentialsException` | +| 4 | Cargar `User` por `account.getId()`. | `UserNotFoundException` | +| 5 | Emitir JWT con `TokenServicePort.generateToken(userId)`. | — | +| 6 | Crear y persistir nuevo `RefreshToken`. | — | +| 7 | Retornar `AuthenticatedUserResponse` con tokens. | — | + +--- + +**`VerifyEmailCommandHandler`** + +| Campo | Detalle | +|------------------------|--------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.application.authentication.verify` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `VerifyEmailCommand` | +| **Retorna** | `void` | +| **Propósito** | Verifica el OTP de correo y activa la cuenta. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|----------------------------------------------------|------------------------------------| +| 1 | Cargar `Account` por email. | `AccountNotFoundException` | +| 2 | Llamar `account.verifyEmail(code, Instant.now())`. | `InvalidVerificationCodeException` | +| 3 | Persistir `Account` actualizado. | — | + +--- + +**`ResendVerificationCodeCommandHandler`** + +| Campo | Detalle | +|------------------------|-------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.application.authentication.resend` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `ResendVerificationCodeCommand` | +| **Retorna** | `void` | +| **Propósito** | Genera un nuevo OTP y publica evento para reenviar el correo de verificación. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|-------------------------------------------------------------------------|----------------------------------| +| 1 | Cargar `Account` por email. | `AccountNotFoundException` | +| 2 | Verificar que la cuenta esté en estado `PENDING_VERIFICATION`. | `BusinessRuleViolationException` | +| 3 | Generar nuevo OTP con `VerificationServicePort`. | — | +| 4 | Llamar `account.generateVerificationCode(code, expiresAt)` y persistir. | — | +| 5 | Publicar `EmailVerificationRequestedEvent`. | — | + +--- + +**`RefreshSessionCommandHandler`** + +| Campo | Detalle | +|------------------------|---------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.application.authentication.refresh` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `RefreshSessionCommand` | +| **Retorna** | `AuthenticatedUserResponse` | +| **Propósito** | Valida el refresh token, lo rota y emite un nuevo access token JWT. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|---------------------------------------------------------------|--------------------------------| +| 1 | Cargar `RefreshToken` por hash del token recibido. | `InvalidRefreshTokenException` | +| 2 | Verificar validez con `refreshToken.isValid(Instant.now())`. | `InvalidRefreshTokenException` | +| 3 | Llamar `refreshToken.rotate()` y persistir el token revocado. | — | +| 4 | Crear nuevo `RefreshToken` y persistir. | — | +| 5 | Cargar `User` y emitir JWT con `TokenServicePort`. | — | +| 6 | Retornar `AuthenticatedUserResponse` con los nuevos tokens. | — | + +--- + +**`RevokeRefreshTokenCommandHandler`** + +| Campo | Detalle | +|------------------------|--------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.application.authentication.signout` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `RevokeRefreshTokenCommand` | +| **Retorna** | `void` | +| **Propósito** | Revoca el refresh token del usuario (sign-out). | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|----------------------------------------------------------|--------------------------------| +| 1 | Cargar `RefreshToken` por hash del token. | `InvalidRefreshTokenException` | +| 2 | Llamar `refreshToken.revoke(Instant.now())` y persistir. | — | + +--- + +**`UpdateUserProfileCommandHandler`** + +| Campo | Detalle | +|------------------------|--------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.application.user.updateprofile` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `UpdateUserProfileCommand` | +| **Retorna** | `User` | +| **Propósito** | Actualiza nombre y foto de perfil del usuario autenticado. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|--------------------------------------------------------------|-------------------------| +| 1 | Cargar `User` por userId. | `UserNotFoundException` | +| 2 | Llamar `user.updateProfile(firstName, lastName, avatarUrl)`. | — | +| 3 | Persistir `User` actualizado. | — | + +--- + +**`UpdateUserPreferencesCommandHandler`** + +| Campo | Detalle | +|------------------------|--------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.iam.application.user.updatepreferences` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `UpdateUserPreferencesCommand` | +| **Retorna** | `User` | +| **Propósito** | Actualiza las preferencias de navegación del usuario autenticado. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|----------------------------------------------------------------------|---------------------------------------------------------| +| 1 | Cargar `User` por userId. | `UserNotFoundException` | +| 2 | Construir `UserPreferences(lastVisitedOrgId, lastVisitedProjectId)`. | `InvalidValueException` si algún campo es cadena vacía. | +| 3 | Llamar `user.updatePreferences(preferences)` y persistir. | — | + +--- + +**Query Handlers** + +| Clase | Paquete | Query que recibe | Retorna | Notas | +|------------------------------------|-----------------------------|-----------------------------|---------|---------------------------------------------------------------------------------------------| +| `GetAuthenticatedUserQueryHandler` | `application/user/queries/` | `GetAuthenticatedUserQuery` | `User` | Obtiene `userId` del `SecurityContextHolder` y carga `User`. Lanza `UserNotFoundException`. | +| `GetUserByIdQueryHandler` | `application/user/queries/` | `GetUserByIdQuery` | `User` | Carga `User` por ID. Lanza `UserNotFoundException`. | +| `GetUserByAccountIdQueryHandler` | `application/user/queries/` | `GetUserByAccountIdQuery` | `User` | Carga `User` por `accountId`. Usado internamente entre handlers. | + +--- + +**Event Listeners** + +| Clase | Evento que escucha | Qué hace | Puertos que usa | +|-------------------------------------------|-----------------------------------|---------------------------------------------------------------|--------------------------------| +| `EmailVerificationRequestedEventListener` | `EmailVerificationRequestedEvent` | Envía correo de verificación con OTP mediante plantilla HTML. | `EmailNotificationServicePort` | + +--- + +**Output Ports** + +**Repository Ports** — `application/ports/repositories/`: + +| Interfaz | Método | Firma | Descripción | +|------------------------------|---------------------|-------------------------------------------------------|--------------------------------------------| +| `AccountRepositoryPort` | `save` | `Account save(Account account)` | Persiste o actualiza. | +| | `findById` | `Optional findById(String id)` | Busca por ID. | +| | `findByEmail` | `Optional findByEmail(Email email)` | Busca por email. | +| | `existsByEmail` | `boolean existsByEmail(Email email)` | Verifica unicidad de email. | +| `UserRepositoryPort` | `save` | `User save(User user)` | Persiste o actualiza. | +| | `findById` | `Optional findById(String id)` | Busca por ID. | +| | `findByAccountId` | `Optional findByAccountId(AccountId accountId)` | Busca por cuenta. | +| `RefreshTokenRepositoryPort` | `save` | `RefreshToken save(RefreshToken token)` | Persiste o actualiza. | +| | `findByTokenHash` | `Optional findByTokenHash(String hash)` | Busca por hash SHA-256. | +| | `deleteAllByUserId` | `void deleteAllByUserId(String userId)` | Limpieza de tokens al eliminar un usuario. | + +**Service Ports** — `application/ports/`: + +| Interfaz | Paquete | Métodos clave | Implementación en infra | +|--------------------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------|---------------------------------| +| `TokenServicePort` | `ports/token/` | `generateToken(String userId): String`, `validateToken(String token): boolean`, `getUserIdFromToken(String token): String` | `JwtTokenServiceAdapter` | +| `HashingServicePort` | `ports/hashing/` | `encode(String raw): String`, `matches(String raw, String hash): boolean` | `BCryptHashingServiceAdapter` | +| `VerificationServicePort` | `ports/verification/` | `generateCode(): String`, `generateExpirationMinutes(): int` | `OtpVerificationServiceAdapter` | +| `EmailNotificationServicePort` | `ports/email/` | `sendVerificationEmail(String to, String code, int expirationMinutes): void` | `SmtpEmailNotificationAdapter` | + +--- + +### 5.1.4. Infrastructure Layer + +Esta capa contiene las implementaciones técnicas de los puertos definidos en la capa de aplicación. El dominio no conoce esta capa. + +**JPA Repositories** + +| Clase | Extiende | Implementa | Propósito | +|--------------------------|---------------------------------------|------------------------------|------------------------------------------------------------------------------| +| `AccountRepository` | `JpaRepository` | `AccountRepositoryPort` | Persistencia de cuentas. Spring Data genera queries por VO `Email` embebido. | +| `UserRepository` | `JpaRepository` | `UserRepositoryPort` | Persistencia de perfiles de usuario. Soporta búsqueda por `AccountId`. | +| `RefreshTokenRepository` | `JpaRepository` | `RefreshTokenRepositoryPort` | Persistencia de tokens. Soporta búsqueda por hash y borrado por userId. | + +**Métodos derivados (Spring Data):** + +| Repositorio | Firma | Descripción | +|--------------------------|------------------------------------------------------------|---------------------------------------------| +| `AccountRepository` | `Optional findByEmail(Email email)` | Búsqueda por VO embebido. | +| `AccountRepository` | `boolean existsByEmail(Email email)` | Verificación de unicidad de email. | +| `UserRepository` | `Optional findByAccountId(AccountId accountId)` | Búsqueda de perfil por referencia a cuenta. | +| `RefreshTokenRepository` | `Optional findByTokenHash(String tokenHash)` | Búsqueda de token por hash SHA-256. | +| `RefreshTokenRepository` | `void deleteAllByUserId(String userId)` | Limpieza de tokens al eliminar un usuario. | + +--- + +**Adapters Externos** + +| Clase | Implementa | Servicio externo | Tecnología | Propósito | +|---------------------------------|--------------------------------|-------------------------|---------------------------------------------------------------|-----------------------------------------------------------------| +| `JwtTokenServiceAdapter` | `TokenServicePort` | — | JJWT (HS256, clave y expiración configurables por properties) | Emite y valida JWT. Claims: `sub`, `userId`. | +| `BCryptHashingServiceAdapter` | `HashingServicePort` | — | Spring Security `BCryptPasswordEncoder` | Hashea y verifica contraseñas con BCrypt. | +| `OtpVerificationServiceAdapter` | `VerificationServicePort` | — | `SecureRandom` | Genera códigos OTP numéricos de 6 dígitos con TTL configurable. | +| `SmtpEmailNotificationAdapter` | `EmailNotificationServicePort` | SMTP (SendGrid / Gmail) | Spring Mail | Envía correo de verificación con plantilla HTML. | + +--- + +**Configuración de Seguridad** + +| Clase | Propósito | +|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `WebSecurityConfiguration` | Define la cadena de filtros Spring Security: CORS habilitado, CSRF deshabilitado, sesión stateless, `permitAll` en `/api/v1/authentication/**` y Swagger UI. Registra `BearerAuthorizationRequestFilter`. | +| `BearerAuthorizationRequestFilter` | Intercepta cada request, extrae el Bearer token del header `Authorization`, lo valida con `TokenServicePort` y establece la autenticación en el `SecurityContextHolder`. | +| `UnauthorizedRequestHandlerEntryPoint` | Responde con `401 Unauthorized` ante cualquier acceso sin token válido. | +| `UserDetailsServiceImpl` | Implementa `UserDetailsService` de Spring Security. Carga `Account` por email para el proceso de autenticación del filtro. | + +**JWT (Access Token):** +- Claims incluidos: `sub` (email), `userId`. +- No incluye `orgId` ni permisos de organización; la autorización por organización se resuelve en el BC Workspace Management. +- Expiración configurable por properties de entorno. + +### 5.1.6. Bounded Context Software Architecture Component Level Diagrams + +En esta sección se presenta el diagrama de componentes C4 (Nivel 3) del BC IAM. El container es el módulo Spring Modulith completo. Los componentes reflejan la descomposición por capas y sus interacciones principales. + +![IAM Component Diagram](assets/diagrams/iam/iam-component.png) + +### 5.1.7. Bounded Context Software Architecture Code Level Diagrams + +#### 5.1.7.1. Bounded Context Domain Layer Class Diagrams + +En esta sección se presenta el diagrama de clases UML del Domain Layer del BC IAM. Incluye los tres Aggregate Roots, los Value Objects, las enumeraciones y los Domain Events, con visibilidades completas, multiplicidades y relaciones de herencia, composición y dependencia. + +![IAM Domain Class Diagram](assets/diagrams/iam/iam-class.png) + +#### 5.1.7.2. Bounded Context Database Design Diagram + +En esta sección se presenta el diagrama de base de datos del BC IAM. Incluye las tres tablas que persisten los Aggregate Roots, con sus columnas, constraints y relaciones. El VO `Email` se persiste como columna embebida en `accounts`. El VO `UserPreferences` se persiste como columnas embebidas en `users`. + +![IAM Database Diagram](assets/diagrams/iam/iam-database.png) + +## 5.2. Bounded Context: Billing and Subscriptions + +El BC Billing and Subscriptions gestiona el ciclo de vida de las suscripciones de las organizaciones en Reqs-AI. Es responsable desde la asignación del plan gratuito hasta las transiciones de plan, cancelaciones y seguimiento del consumo de tokens. Implementa el patrón `PaymentProviderRef` para mantenerse desacoplado del proveedor de pagos concreto (Stripe, culqi, etc.). + +### 5.2.1. Domain Layer + +Esta capa contiene las reglas de negocio de suscripciones, cuotas de uso y ciclo de vida de planes, sin dependencia de frameworks externos. + +**Aggregate Roots** + +**Aggregate: `Subscription`** + +| Campo | Detalle | +|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.domain.model.aggregates` | +| **Extiende** | `AbstractAggregateRoot` | +| **Propósito** | Representa la suscripción de una organización a un plan. Controla las transiciones de plan, cancelaciones, reactivaciones y el seguimiento del consumo de tokens contra la cuota mensual. | +| **Anotaciones JPA** | `@Entity`, `@Table(name = "subscriptions")` | + +**Atributos:** + +| Atributo | Tipo | Columna JPA | Descripción | +|----------------------|-----------------------|------------------------|--------------------------------------------------------------| +| `id` | `SubscriptionId` | `id` | Identificador único de la suscripción (UUID). | +| `organizationId` | `OrganizationId` | `organization_id` | Referencia a la organización propietaria. | +| `planType` | `PlanType` | `plan_type` | Plan actual: FREE, PRO o ENTERPRISE. | +| `status` | `SubscriptionStatus` | `status` | Estado actual de la suscripción en su ciclo de vida. | +| `providerRef` | `PaymentProviderRef?` | `@Embedded` | Referencia al proveedor de pagos externo. Nulo en plan FREE. | +| `currentPeriodStart` | `Instant` | `current_period_start` | Inicio del período de facturación vigente. | +| `currentPeriodEnd` | `Instant` | `current_period_end` | Fin del período de facturación vigente. | +| `tokenQuotaUsed` | `Long` | `token_quota_used` | Tokens consumidos en el período actual. | +| `cancelledAt` | `Instant?` | `cancelled_at` | Fecha de cancelación. Nulo si no ha sido cancelada. | + +**Constructores:** + +| Constructor | Visibilidad | Propósito | +|-----------------------------------------------|-------------|---------------------------------------------------------------------------------| +| `protected Subscription()` | `protected` | Para JPA. No instanciar directamente. | +| `Subscription(OrganizationId organizationId)` | `public` | Crea suscripción gratuita en estado `ACTIVE`. Inicializa `tokenQuotaUsed` en 0. | + +**Métodos de negocio:** + +| Método | Visibilidad | Parámetros | Retorna | Descripción | Excepciones lanzadas | +|--------------------------------------------------------------------------------------------------------|-------------|-------------------------------------------------------|---------|------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| `upgradeTo(PlanType planType, PaymentProviderRef providerRef, Instant periodStart, Instant periodEnd)` | `public` | `planType`, `providerRef`, `periodStart`, `periodEnd` | `void` | Transiciona a un plan superior y registra la referencia del proveedor. | `CannotUpgradeSubscriptionException` si está cancelada o ya tiene ese plan. | +| `cancel(Instant cancelledAt)` | `public` | `cancelledAt: Instant` | `void` | Cancela la suscripción. | `CannotCancelSubscriptionException` si ya está cancelada. | +| `reactivate(Instant periodStart, Instant periodEnd)` | `public` | `periodStart`, `periodEnd: Instant` | `void` | Reactiva la suscripción cancelada. | `CannotReactivateSubscriptionException` si no está en estado `CANCELLED`. | +| `incrementTokenUsage(Long tokens)` | `public` | `tokens: Long` | `void` | Suma tokens al consumo del período actual. | — | +| `resetQuota()` | `public` | — | `void` | Reinicia `tokenQuotaUsed` a 0 al inicio de nuevo período. | — | +| `applyProviderRef(PaymentProviderRef providerRef)` | `public` | `providerRef: PaymentProviderRef` | `void` | Actualiza la referencia del proveedor externo. | — | + +**Métodos de consulta:** + +| Método | Visibilidad | Retorna | Descripción | +|-----------------------------------|-------------|-----------|------------------------------------------| +| `isActive()` | `public` | `boolean` | `true` si el status es `ACTIVE`. | +| `isCancelled()` | `public` | `boolean` | `true` si el status es `CANCELLED`. | +| `isPastDue()` | `public` | `boolean` | `true` si el status es `PAST_DUE`. | +| `isQuotaExceeded(Long maxTokens)` | `public` | `boolean` | `true` si `tokenQuotaUsed >= maxTokens`. | +| `isFree()` | `public` | `boolean` | `true` si `planType` es `FREE`. | + +**Relaciones:** + +| Relación | Tipo | Multiplicidad | Detalles | +|---------------------------------------|---------------------------|---------------|---------------------------------| +| `Subscription` → `OrganizationId` | Referencia por ID | 1..1 | Mantiene frontera de aggregate. | +| `Subscription` → `PaymentProviderRef` | Composición (`@Embedded`) | 0..1 | VO embebido, nulo en plan FREE. | + +--- + +**Value Objects** + +**Value Object: `PaymentProviderRef`** + +| Campo | Detalle | +|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.domain.model.valueobjects` | +| **Tipo Java** | `record` | +| **Propósito** | Encapsula la referencia a un proveedor de pagos externo. Permite cambiar de proveedor (Stripe → Culqi) sin modificar el aggregate `Subscription`. | + +**Campos:** + +| Campo | Tipo | Descripción | +|--------------|-------------------|---------------------------------------------------| +| `provider` | `PaymentProvider` | Proveedor que emitió la suscripción externa. | +| `externalId` | `String` | ID de la suscripción en el sistema del proveedor. | + +**Validaciones en compact constructor:** + +| Regla | Excepción lanzada | Error Code | +|----------------------------------------|-------------------------|--------------------------------| +| `provider` no puede ser nulo | `InvalidValueException` | `INVALID_PAYMENT_PROVIDER_REF` | +| `externalId` no puede ser nulo o vacío | `InvalidValueException` | `INVALID_PAYMENT_PROVIDER_REF` | + +--- + +**Value Object: `PaymentProvider`** + +| Campo | Detalle | +|---------------|---------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.domain.model.valueobjects` | +| **Tipo Java** | `enum` | +| **Propósito** | Identifica el proveedor de pagos externo con el que se gestiona la suscripción. | + +**Valores:** + +| Valor | Descripción en el negocio | +|----------------|--------------------------------------------| +| `STRIPE` | Proveedor Stripe (mercado internacional). | +| `CULQI` | Proveedor Culqi (mercado latinoamericano). | +| `PAYPAL` | Proveedor PayPal. | +| `MERCADO_PAGO` | Proveedor Mercado Pago. | + +--- + +**Value Object: `PlanType`** + +| Campo | Detalle | +|---------------|----------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.domain.model.valueobjects` | +| **Tipo Java** | `enum` | +| **Propósito** | Define los tipos de plan disponibles en Reqs-AI. | + +**Valores:** + +| Valor | Descripción en el negocio | +|--------------|---------------------------------------------------------------------| +| `FREE` | Plan gratuito con cuotas reducidas. Sin proveedor de pagos externo. | +| `PRO` | Plan de pago mensual con cuotas ampliadas. | +| `ENTERPRISE` | Plan corporativo con cuotas máximas y soporte dedicado. | + +--- + +**Value Object: `SubscriptionStatus`** + +| Campo | Detalle | +|---------------|-------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.domain.model.valueobjects` | +| **Tipo Java** | `enum` | +| **Propósito** | Define los estados posibles del ciclo de vida de una suscripción. | + +**Valores:** + +| Valor | Descripción en el negocio | +|-------------|----------------------------------------------------------------------| +| `ACTIVE` | Suscripción activa y vigente. | +| `CANCELLED` | Suscripción cancelada. Acceso puede mantenerse hasta fin de período. | +| `PAST_DUE` | Pago fallido. Acceso restringido hasta regularizar. | +| `TRIALING` | En período de prueba. | + +--- + +**Domain Exceptions** + +| Clase | Extiende | HTTP Status | Error Code | Cuándo se lanza | +|-----------------------------------------|----------------------------------|-------------|----------------------------------|--------------------------------------------------------------| +| `SubscriptionNotFoundException` | `EntityNotFoundException` | 404 | `SUBSCRIPTION_NOT_FOUND` | No se encuentra la suscripción por ID u organizationId. | +| `SubscriptionAlreadyExistsException` | `BusinessRuleViolationException` | 409 | `SUBSCRIPTION_ALREADY_EXISTS` | La organización ya tiene una suscripción activa. | +| `CannotUpgradeSubscriptionException` | `BusinessRuleViolationException` | 409 | `CANNOT_UPGRADE_SUBSCRIPTION` | La suscripción está cancelada o ya tiene el plan solicitado. | +| `CannotCancelSubscriptionException` | `BusinessRuleViolationException` | 409 | `CANNOT_CANCEL_SUBSCRIPTION` | La suscripción ya está cancelada. | +| `CannotReactivateSubscriptionException` | `BusinessRuleViolationException` | 409 | `CANNOT_REACTIVATE_SUBSCRIPTION` | La suscripción no está en estado `CANCELLED`. | +| `TokenQuotaExceededException` | `BusinessRuleViolationException` | 409 | `TOKEN_QUOTA_EXCEEDED` | El consumo de tokens supera la cuota del plan. | + +--- + +**Domain Events** + +| Clase | Paquete | Campos clave | Se publica cuando | Consumido por | +|------------------------------|--------------------------------|------------------------------------------------------------------------|--------------------------------------------------|--------------------------------------------------------------| +| `SubscriptionAssignedEvent` | `billing/api/` | `subscriptionId`, `organizationId`, `planType`, `occurredAt` | Se asigna el plan FREE a una organización nueva. | BC Workspace (aplica `PlanLimits` a la organización). | +| `SubscriptionUpgradedEvent` | `billing/api/` | `subscriptionId`, `organizationId`, `oldPlan`, `newPlan`, `occurredAt` | Se completa `UpgradeSubscriptionCommandHandler`. | BC Workspace (actualiza `PlanLimits`). | +| `SubscriptionCancelledEvent` | `billing/domain/model/events/` | `subscriptionId`, `organizationId`, `occurredAt` | La suscripción pasa a estado `CANCELLED`. | Interno. | +| `TokenQuotaExceededEvent` | `billing/api/` | `subscriptionId`, `organizationId`, `quotaUsed`, `occurredAt` | `tokenQuotaUsed` alcanza el máximo del plan. | BC Req Discovery (bloquea procesamiento de nuevas sesiones). | + +--- + +**Commands** + +| Clase | Paquete | Campos | Handler que lo procesa | +|---------------------------------|--------------------------|------------------------------------------------------------------------------------------------|----------------------------------------| +| `AssignFreeSubscriptionCommand` | `domain/model/commands/` | `organizationId: String` | `AssignFreeSubscriptionCommandHandler` | +| `UpgradeSubscriptionCommand` | `domain/model/commands/` | `subscriptionId: String`, `planType: String`, `providerExternalId: String`, `provider: String` | `UpgradeSubscriptionCommandHandler` | +| `CancelSubscriptionCommand` | `domain/model/commands/` | `subscriptionId: String` | `CancelSubscriptionCommandHandler` | +| `ReactivateSubscriptionCommand` | `domain/model/commands/` | `subscriptionId: String` | `ReactivateSubscriptionCommandHandler` | +| `IncrementTokenUsageCommand` | `domain/model/commands/` | `organizationId: String`, `tokens: Long` | `IncrementTokenUsageCommandHandler` | +| `ResetQuotaCommand` | `domain/model/commands/` | `subscriptionId: String` | `ResetQuotaCommandHandler` | + +**Queries** + +| Clase | Paquete | Campos | Handler que lo procesa | +|--------------------------------------|-------------------------|--------------------------|---------------------------------------------| +| `GetSubscriptionByOrganizationQuery` | `domain/model/queries/` | `organizationId: String` | `GetSubscriptionByOrganizationQueryHandler` | +| `GetSubscriptionByIdQuery` | `domain/model/queries/` | `subscriptionId: String` | `GetSubscriptionByIdQueryHandler` | + +--- + +### 5.2.2. Interface Layer + +Esta capa expone los endpoints REST del BC Billing para consulta y gestión de suscripciones, y recibe webhooks de proveedores de pago. + +**Controllers** + +**`SubscriptionController` (Swagger Interface)** + +| Campo | Detalle | +|-----------------|------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.interfaces.rest.swagger` | +| **Base path** | `ApiVersioning.BASE + "/subscriptions"` → `/api/v1/subscriptions` | +| **Tag OpenAPI** | `"Subscriptions"` | +| **Propósito** | Contrato OpenAPI para consulta y gestión del ciclo de vida de suscripciones. | + +| Método HTTP | Path | Nombre del método | Request DTO | Response DTO | Códigos HTTP | +|-------------|----------------------------------|---------------------------------|---------------------------------|------------------------|-------------------------| +| `GET` | `/organization/{organizationId}` | `getSubscriptionByOrganization` | — (path variable) | `SubscriptionResponse` | 200, 401, 404 | +| `POST` | `/` | `assignFreeSubscription` | `AssignFreeSubscriptionRequest` | `SubscriptionResponse` | 201, 400, 401, 409 | +| `PUT` | `/{id}/upgrade` | `upgradeSubscription` | `UpgradeSubscriptionRequest` | `SubscriptionResponse` | 200, 400, 401, 404, 409 | +| `PUT` | `/{id}/cancel` | `cancelSubscription` | — | `SubscriptionResponse` | 200, 401, 404, 409 | +| `PUT` | `/{id}/reactivate` | `reactivateSubscription` | — | `SubscriptionResponse` | 200, 401, 404, 409 | + +**`SubscriptionControllerImpl` (Implementation)** + +| Campo | Detalle | +|-----------------|------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.interfaces.rest.controllers` | +| **Anotaciones** | `@Slf4j`, `@RestController`, `@RequiredArgsConstructor` | +| **Implementa** | `SubscriptionController` | + +| Handler | Para qué endpoint | +|---------------------------------------------|--------------------------------------| +| `GetSubscriptionByOrganizationQueryHandler` | `GET /organization/{organizationId}` | +| `AssignFreeSubscriptionCommandHandler` | `POST /` | +| `UpgradeSubscriptionCommandHandler` | `PUT /{id}/upgrade` | +| `CancelSubscriptionCommandHandler` | `PUT /{id}/cancel` | +| `ReactivateSubscriptionCommandHandler` | `PUT /{id}/reactivate` | + +--- + +**`BillingWebhookController` (Swagger Interface)** + +| Campo | Detalle | +|-----------------|-------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.interfaces.rest.swagger` | +| **Base path** | `ApiVersioning.BASE + "/billing/webhooks"` → `/api/v1/billing/webhooks` | +| **Tag OpenAPI** | `"Billing Webhooks"` | +| **Propósito** | Recibe notificaciones de eventos de proveedores de pago (pagos exitosos, fallos, renovaciones). | + +| Método HTTP | Path | Nombre del método | Request DTO | Response DTO | Códigos HTTP | +|-------------|-----------|-----------------------|------------------------|--------------|--------------| +| `POST` | `/stripe` | `handleStripeWebhook` | `String` (payload raw) | `void` | 200, 400 | +| `POST` | `/culqi` | `handleCulqiWebhook` | `String` (payload raw) | `void` | 200, 400 | + +**`BillingWebhookControllerImpl` (Implementation)** + +| Campo | Detalle | +|-----------------|------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.interfaces.rest.controllers` | +| **Anotaciones** | `@Slf4j`, `@RestController`, `@RequiredArgsConstructor` | +| **Implementa** | `BillingWebhookController` | +| Handler | Para qué endpoint | +|--------------------------------------------------------------------------|------------------------------------| +| `PaymentProviderPort` | Verificación de firma del webhook. | +| `UpgradeSubscriptionCommandHandler` / `CancelSubscriptionCommandHandler` | Según evento del proveedor. | + +--- + +**Request DTOs** + +| Clase | Paquete | Campos | Validaciones Jakarta | +|---------------------------------|--------------------------------|----------------------------------------------------------------------|----------------------| +| `AssignFreeSubscriptionRequest` | `interfaces/rest/dto/request/` | `organizationId: String` | `@NotBlank` | +| `UpgradeSubscriptionRequest` | `interfaces/rest/dto/request/` | `planType: String`, `provider: String`, `providerExternalId: String` | `@NotBlank` en todos | + +**Response DTOs** + +| Clase | Paquete | Campos | Notas | +|------------------------|---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------| +| `SubscriptionResponse` | `interfaces/rest/dto/response/` | `id: String`, `organizationId: String`, `planType: String`, `status: String`, `provider: String?`, `providerExternalId: String?`, `currentPeriodStart: Instant`, `currentPeriodEnd: Instant`, `tokenQuotaUsed: Long`, `cancelledAt: Instant?` | `@Builder` + `@Schema` | + +**Mappers** + +**Request Mappers:** + +| Clase | Método | Convierte | +|---------------------------------------|----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| `AssignFreeSubscriptionRequestMapper` | `static AssignFreeSubscriptionCommand toCommand(AssignFreeSubscriptionRequest request)` | DTO → Command. | +| `UpgradeSubscriptionRequestMapper` | `static UpgradeSubscriptionCommand toCommand(String id, UpgradeSubscriptionRequest request)` | Combina path variable + body en el Command. Construye `PaymentProviderRef`. | + +**Response Mappers:** + +| Clase | Método | Convierte | +|------------------------------|---------------------------------------------------------------------|---------------------------------------------------------------------------| +| `SubscriptionResponseMapper` | `static SubscriptionResponse toResponse(Subscription subscription)` | Aggregate → DTO. Extrae `provider.name()` y `externalId` del VO embebido. | + +--- + +### 5.2.3. Application Layer + +Esta capa orquesta los casos de uso de Billing. Coordina la validación de negocio, la persistencia y la publicación de eventos sin contener lógica de dominio. + +**Command Handlers** + +**`AssignFreeSubscriptionCommandHandler`** + +| Campo | Detalle | +|------------------------|----------------------------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.application.subscription.assignfree` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `AssignFreeSubscriptionCommand` | +| **Retorna** | `Subscription` | +| **Propósito** | Asigna el plan FREE a una organización recién creada. Generalmente invocado por el BC Workspace al crear una organización. | + +**Dependencias:** + +| Puerto | Para qué se usa | +|------------------------------|------------------------------------------------| +| `SubscriptionRepositoryPort` | Verificar unicidad y persistir `Subscription`. | +| `ApplicationEventPublisher` | Publicar `SubscriptionAssignedEvent`. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|--------------------------------------------------------------|--------------------------------------| +| 1 | Verificar que la organización no tenga ya una suscripción. | `SubscriptionAlreadyExistsException` | +| 2 | Crear `Subscription` con `new Subscription(organizationId)`. | — | +| 3 | Persistir con `subscriptionRepository.save(subscription)`. | — | +| 4 | Publicar `SubscriptionAssignedEvent`. | — | + +--- + +**`UpgradeSubscriptionCommandHandler`** + +| Campo | Detalle | +|------------------------|---------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.application.subscription.upgrade` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `UpgradeSubscriptionCommand` | +| **Retorna** | `Subscription` | +| **Propósito** | Actualiza la suscripción a un plan superior y registra la referencia del proveedor externo. | + +**Dependencias:** + +| Puerto | Para qué se usa | +|------------------------------|---------------------------------------------| +| `SubscriptionRepositoryPort` | Cargar y persistir `Subscription`. | +| `PaymentProviderPort` | Confirmar el pago con el proveedor externo. | +| `ApplicationEventPublisher` | Publicar `SubscriptionUpgradedEvent`. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|---------------------------------------------------------------------------------|--------------------------------------| +| 1 | Cargar `Subscription` por ID. | `SubscriptionNotFoundException` | +| 2 | Confirmar pago con `PaymentProviderPort.confirmUpgrade()`. | `PaymentProviderException` | +| 3 | Construir `PaymentProviderRef(provider, externalId)`. | — | +| 4 | Llamar `subscription.upgradeTo(planType, providerRef, periodStart, periodEnd)`. | `CannotUpgradeSubscriptionException` | +| 5 | Persistir y publicar `SubscriptionUpgradedEvent`. | — | + +--- + +**`CancelSubscriptionCommandHandler`** + +| Campo | Detalle | +|------------------------|--------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.application.subscription.cancel` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `CancelSubscriptionCommand` | +| **Retorna** | `void` | +| **Propósito** | Cancela la suscripción activa. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|----------------------------------------------------|-------------------------------------| +| 1 | Cargar `Subscription` por ID. | `SubscriptionNotFoundException` | +| 2 | Llamar `subscription.cancel(Instant.now())`. | `CannotCancelSubscriptionException` | +| 3 | Persistir y publicar `SubscriptionCancelledEvent`. | — | + +--- + +**`ReactivateSubscriptionCommandHandler`** + +| Campo | Detalle | +|------------------------|--------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.application.subscription.reactivate` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `ReactivateSubscriptionCommand` | +| **Retorna** | `Subscription` | +| **Propósito** | Reactiva una suscripción previamente cancelada. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|-----------------------------------------------------------|-----------------------------------------| +| 1 | Cargar `Subscription` por ID. | `SubscriptionNotFoundException` | +| 2 | Llamar `subscription.reactivate(periodStart, periodEnd)`. | `CannotReactivateSubscriptionException` | +| 3 | Persistir `Subscription` actualizado. | — | + +--- + +**`IncrementTokenUsageCommandHandler`** + +| Campo | Detalle | +|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.application.subscription.tokenusage` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `IncrementTokenUsageCommand` | +| **Retorna** | `void` | +| **Propósito** | Incrementa el consumo de tokens de la organización. Publica evento si se alcanza la cuota. Invocado internamente desde el BC Req Discovery. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|-----------------------------------------------------------------------------------|---------------------------------| +| 1 | Cargar `Subscription` por `organizationId`. | `SubscriptionNotFoundException` | +| 2 | Llamar `subscription.incrementTokenUsage(tokens)`. | — | +| 3 | Persistir `Subscription`. | — | +| 4 | Si `subscription.isQuotaExceeded(maxTokens)`, publicar `TokenQuotaExceededEvent`. | — | + +--- + +**`ResetQuotaCommandHandler`** + +| Campo | Detalle | +|------------------------|-------------------------------------------------------------------------------------------------------------------------------------| +| **Paquete** | `com.kntrosoft.reqsai.billing.application.subscription.resetquota` | +| **Anotaciones** | `@Slf4j`, `@Service`, `@RequiredArgsConstructor`, `@Transactional` | +| **Command que recibe** | `ResetQuotaCommand` | +| **Retorna** | `void` | +| **Propósito** | Reinicia el contador de tokens al inicio de cada período de facturación. Invocado por un scheduler mensual o webhook del proveedor. | + +**Flujo:** + +| Paso | Acción | Excepción lanzada | +|------|-------------------------------------------------|---------------------------------| +| 1 | Cargar `Subscription` por ID. | `SubscriptionNotFoundException` | +| 2 | Llamar `subscription.resetQuota()` y persistir. | — | + +--- + +**Query Handlers** + +| Clase | Paquete | Query que recibe | Retorna | Notas | +|---------------------------------------------|-------------------------------------|--------------------------------------|----------------|-----------------------------------------------------| +| `GetSubscriptionByOrganizationQueryHandler` | `application/subscription/queries/` | `GetSubscriptionByOrganizationQuery` | `Subscription` | Lanza `SubscriptionNotFoundException` si no existe. | +| `GetSubscriptionByIdQueryHandler` | `application/subscription/queries/` | `GetSubscriptionByIdQuery` | `Subscription` | Lanza `SubscriptionNotFoundException` si no existe. | + +--- + +**Event Listeners** + +| Clase | Evento que escucha | Qué hace | Puertos que usa | +|-------------------------------------|-----------------------------|---------------------------------------------------------------------------------------|------------------------------------| +| `SubscriptionAssignedEventListener` | `SubscriptionAssignedEvent` | Notifica al BC Workspace para aplicar los `PlanLimits` correspondientes al plan FREE. | `WorkspaceModuleApi` (in-process). | +| `SubscriptionUpgradedEventListener` | `SubscriptionUpgradedEvent` | Notifica al BC Workspace para actualizar los `PlanLimits` al nuevo plan. | `WorkspaceModuleApi` (in-process). | + +--- + +**Output Ports** + +**Repository Ports** — `application/ports/repositories/`: + +| Interfaz | Método | Firma | Descripción | +|------------------------------|--------------------------|----------------------------------------------------------------------|-------------------------------------| +| `SubscriptionRepositoryPort` | `save` | `Subscription save(Subscription subscription)` | Persiste o actualiza. | +| | `findById` | `Optional findById(String id)` | Busca por ID. | +| | `findByOrganizationId` | `Optional findByOrganizationId(String organizationId)` | Busca por organización. | +| | `existsByOrganizationId` | `boolean existsByOrganizationId(String organizationId)` | Verifica unicidad por organización. | + +**Service Ports** — `application/ports/`: + +| Interfaz | Paquete | Métodos clave | Implementación en infra | +|-----------------------|------------------|------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------| +| `PaymentProviderPort` | `ports/payment/` | `confirmUpgrade(String externalId, PaymentProvider provider): boolean`, `cancelExternal(PaymentProviderRef ref): void` | `StripePaymentProviderAdapter`, `CulqiPaymentProviderAdapter` | + +--- + +### 5.2.4. Infrastructure Layer + +Esta capa contiene las implementaciones técnicas de los puertos definidos en Billing. El dominio no conoce esta capa. + +**JPA Repositories** + +| Clase | Extiende | Implementa | Propósito | +|--------------------------|---------------------------------------|------------------------------|---------------------------------------------------------------------------------| +| `SubscriptionRepository` | `JpaRepository` | `SubscriptionRepositoryPort` | Persistencia de suscripciones. Spring Data genera queries por `organizationId`. | + +**Métodos derivados (Spring Data):** + +| Repositorio | Firma | Descripción | +|--------------------------|----------------------------------------------------------------------|------------------------------------------------------| +| `SubscriptionRepository` | `Optional findByOrganizationId(String organizationId)` | Búsqueda de la suscripción vigente por organización. | +| `SubscriptionRepository` | `boolean existsByOrganizationId(String organizationId)` | Verificación de unicidad. | + +--- + +**Adapters Externos** + +| Clase | Implementa | Servicio externo | Tecnología | Propósito | +|--------------------------------|-----------------------|------------------|------------------------------|------------------------------------------------------------------------------------------------------------| +| `StripePaymentProviderAdapter` | `PaymentProviderPort` | Stripe API | Stripe Java SDK | Confirma pagos, crea y cancela suscripciones en Stripe. Verifica firma de webhooks con `Stripe-Signature`. | +| `CulqiPaymentProviderAdapter` | `PaymentProviderPort` | Culqi API | Culqi Java SDK / HTTP client | Confirma pagos y gestiona suscripciones en Culqi para el mercado latinoamericano. | + +**Configuración de infraestructura:** + +| Clase | Propósito | +|---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `BillingSchedulerConfiguration` | Define el scheduler mensual (`@Scheduled`) que dispara `ResetQuotaCommand` para todas las suscripciones activas al inicio de cada período de facturación. | + +### 5.2.6. Bounded Context Software Architecture Component Level Diagrams + +En esta sección se presenta el diagrama de componentes C4 (Nivel 3) del BC Billing and Subscriptions. El container es el módulo Spring Modulith completo. Los componentes reflejan la descomposición por capas y sus interacciones, incluyendo el scheduler de reset de cuota y la integración con proveedores de pago externos. + +![Billing Component Diagram](assets/diagrams/billing/billing-component.png) + +### 5.2.7. Bounded Context Software Architecture Code Level Diagrams + +#### 5.2.7.1. Bounded Context Domain Layer Class Diagrams + +En esta sección se presenta el diagrama de clases UML del Domain Layer del BC Billing. Incluye el Aggregate Root `Subscription`, los Value Objects (`PaymentProviderRef`, `SubscriptionId`, `OrganizationId`), las enumeraciones (`PlanType`, `SubscriptionStatus`, `PaymentProvider`), los Domain Events publicados al Api package y las excepciones de dominio con su jerarquía de herencia desde el Shared Kernel. + +![Billing Domain Class Diagram](assets/diagrams/billing/billing-class.png) + +#### 5.2.7.2. Bounded Context Database Design Diagram + +En esta sección se presenta el diagrama de base de datos del BC Billing. La persistencia se reduce a una única tabla `subscriptions`, que almacena el ciclo de vida de la suscripción de cada organización. El VO `PaymentProviderRef` se persiste como columnas embebidas (`payment_provider`, `payment_external_id`). La columna `token_quota_used` acumula el consumo de tokens del período actual y se reinicia vía scheduler mensual. + +![Billing Database Diagram](assets/diagrams/billing/billing-database.png) + +## 5.3. Bounded Context: Workspace Management + +### 5.3.1. Domain Layer + +El Bounded Context de Workspace Management gestiona las organizaciones, miembros, proyectos, roles, documentos y glosarios. Es el núcleo estructural de la plataforma: todo el trabajo de elicitación de requisitos ocurre dentro de un proyecto, que a su vez pertenece a una organización. Este BC también aplica los límites de plan definidos por Billing sobre cada organización. + +**Aggregate Roots** + +--- + +**`Organization`** — tabla: `organizations` + +Representa la unidad raíz de tenencia multi-organizacional. Contiene los límites de plan activos que controlan los recursos disponibles. + +| Campo | Tipo | Descripción | +|--------------|------------------|----------------------------------------------------------| +| `id` | `OrganizationId` | Identificador único de la organización | +| `name` | `String` | Nombre visible de la organización | +| `slug` | `String` | Identificador URL único (inmutable tras creación) | +| `ownerId` | `UserId` | Referencia al usuario propietario | +| `status` | `OrgStatus` | Estado: `ACTIVE`, `INACTIVE`, `DELETED` | +| `planLimits` | `PlanLimits` | Límites operativos actuales según el plan de facturación | + +| Método | Descripción | +|--------------------------------|---------------------------------------------------------| +| `rename(name)` | Actualiza el nombre de la organización | +| `updateLimits(limits)` | Reemplaza los límites de plan tras un evento de Billing | +| `deactivate()` | Cambia el estado a `INACTIVE` | +| `reactivate()` | Cambia el estado a `ACTIVE` | +| `delete()` | Cambia el estado a `DELETED` | + +| Excepción lanzada | Condición de disparo | +|------------------------------------------|---------------------------------| +| `OrganizationSlugAlreadyExistsException` | El slug ya existe en el sistema | + +--- + +**`Member`** — tabla: `members` + +Representa la pertenencia de un usuario a una organización. Los fundadores tienen `invitedBy` e `invitedAt` en `null`; los invitados siempre los tienen definidos. + +| Campo | Tipo | Descripción | +|------------------|------------------|------------------------------------------------------------| +| `id` | `MemberId` | Identificador único de membresía | +| `organizationId` | `OrganizationId` | Organización a la que pertenece | +| `userId` | `UserId` | Usuario representado | +| `role` | `OrgRole` | Rol en la organización: `OWNER`, `ADMIN`, `MEMBER` | +| `status` | `MemberStatus` | Estado: `ACTIVE`, `PENDING`, `INACTIVE` | +| `invitedBy` | `UserId?` | Usuario que realizó la invitación (`null` para fundadores) | +| `invitedAt` | `Instant?` | Momento de la invitación (`null` para fundadores) | + +| Método | Descripción | +|-------------------------|-----------------------------------------------------------| +| `changeRole(role)` | Cambia el rol del miembro dentro de la organización | +| `accept()` | Cambia el estado de `PENDING` a `ACTIVE` | +| `deactivate()` | Cambia el estado a `INACTIVE` | +| `reactivate()` | Cambia el estado a `ACTIVE` | + +| Excepción lanzada | Condición de disparo | +|------------------------------------|----------------------------------------------------| +| `MemberAlreadyExistsException` | El usuario ya es miembro de la organización | +| `MemberPlanLimitExceededException` | Se alcanzó el límite `maxMembers` del plan | + +--- + +**`Project`** — tabla: `projects` + +Agrupa todo el trabajo de elicitación de requisitos. Contiene el perfil técnico del proyecto y las restricciones definidas por el equipo. El `descriptionEmbedding` permite búsqueda semántica entre proyectos. + +| Campo | Tipo | Descripción | +|------------------------|---------------------------|-----------------------------------------------------------------| +| `id` | `ProjectId` | Identificador único del proyecto | +| `organizationId` | `OrganizationId` | Organización propietaria | +| `name` | `String` | Nombre del proyecto (único dentro de la organización) | +| `description` | `String` | Descripción del alcance del proyecto | +| `technicalProfile` | `TechnicalProfile` | Perfil técnico: lenguajes, frameworks, arquitectura, dominio | +| `status` | `ProjectStatus` | Estado: `ACTIVE`, `ARCHIVED` | +| `descriptionEmbedding` | `List?` | Vector embedding de la descripción para búsqueda semántica | +| `constraints` | `List` | Restricciones técnicas del proyecto (entidades compuestas) | + +| Método | Descripción | +|------------------------------------------------------|----------------------------------------------------| +| `updateDetails(name, description, technicalProfile)` | Actualiza metadatos del proyecto | +| `updateEmbedding(embedding)` | Reemplaza el vector de embedding de la descripción | +| `addConstraint(description)` | Agrega una nueva restricción técnica | +| `removeConstraint(constraintId)` | Elimina una restricción existente | +| `archive()` | Cambia el estado a `ARCHIVED` | +| `restore()` | Cambia el estado a `ACTIVE` | + +| Excepción lanzada | Condición de disparo | +|---------------------------------------|---------------------------------------------------------| +| `ProjectNameAlreadyExistsException` | El nombre ya existe dentro de la misma organización | +| `ProjectPlanLimitExceededException` | Se alcanzó el límite `maxProjects` del plan | + +--- + +**`ProjectRole`** — tabla: `project_roles` + +Define un conjunto de permisos asignables a los miembros de un proyecto. Cada proyecto puede tener múltiples roles personalizados. + +| Campo | Tipo | Descripción | +|---------------|-------------------|--------------------------------------------| +| `id` | `ProjectRoleId` | Identificador único del rol | +| `projectId` | `ProjectId` | Proyecto al que pertenece el rol | +| `name` | `String` | Nombre del rol (único dentro del proyecto) | +| `permissions` | `Set` | Conjunto de permisos asignados | + +| Método | Descripción | +|----------------------------------|-----------------------------------| +| `rename(name)` | Cambia el nombre del rol | +| `updatePermissions(permissions)` | Reemplaza el conjunto de permisos | + +| Excepción lanzada | Condición de disparo | +|-----------------------------------------|-----------------------------------------------| +| `ProjectRoleNameAlreadyExistsException` | El nombre del rol ya existe en el proyecto | + +--- + +**`ProjectMember`** — tabla: `project_members` + +Relaciona un miembro de la organización con un proyecto y le asigna un rol. Los campos `assignedBy` y `assignedAt` son campos de dominio propios (no campos de auditoría heredados). + +| Campo | Tipo | Descripción | +|----------------|-------------------|----------------------------------------------------------| +| `id` | `ProjectMemberId` | Identificador único de la membresía de proyecto | +| `projectId` | `ProjectId` | Proyecto al que pertenece | +| `memberId` | `MemberId` | Miembro de la organización asignado | +| `roleId` | `ProjectRoleId` | Rol asignado dentro del proyecto | +| `assignedBy` | `UserId` | Usuario que realizó la asignación | +| `assignedAt` | `Instant` | Momento de la asignación | + +| Método | Descripción | +|-------------------------|---------------------------------------------------------| +| `changeRole(roleId)` | Reasigna el rol del miembro dentro del proyecto | + +| Excepción lanzada | Condición de disparo | +|----------------------------------------|-------------------------------------------------| +| `ProjectMemberAlreadyExistsException` | El miembro ya está asignado al proyecto | + +--- + +**`ProjectDocument`** — tabla: `project_documents` + +Representa un documento cargado en el contexto de un proyecto (especificaciones, manuales, contratos). El responsable de carga se obtiene de `createdBy` heredado de `AbstractAggregateRoot`. + +| Campo | Tipo | Descripción | +|-------------|-----------------------|----------------------------------------------------------| +| `id` | `ProjectDocumentId` | Identificador único del documento | +| `projectId` | `ProjectId` | Proyecto al que pertenece | +| `name` | `String` | Nombre descriptivo del documento | +| `url` | `String` | URL de almacenamiento (S3 o equivalente) | +| `mimeType` | `String` | Tipo MIME del archivo | +| `status` | `DocumentStatus` | Estado: `ACTIVE`, `ARCHIVED` | + +| Método | Descripción | +|-------------|-------------------------------------| +| `archive()` | Cambia el estado a `ARCHIVED` | +| `restore()` | Cambia el estado a `ACTIVE` | + +| Excepción lanzada | Condición de disparo | +|----------------------------------------|----------------------------------------------------| +| `DocumentPlanLimitExceededException` | Se alcanzó el límite `maxDocumentsPerProject` | + +--- + +**`Glossary`** — tabla: `glossaries` + +Glosario de términos técnicos de un proyecto. Se crea automáticamente al crear el proyecto. Gestiona la composición de `GlossaryTerm` como entidades internas. + +| Campo | Tipo | Descripción | +|-------------|----------------------|-----------------------------------| +| `id` | `GlossaryId` | Identificador único del glosario | +| `projectId` | `ProjectId` | Proyecto al que pertenece (1:1) | +| `terms` | `List` | Términos del glosario (entidades) | + +| Método | Descripción | +|------------------------------------------------|-------------------------------------------------------| +| `addTerm(term, definition, synonyms, addedBy)` | Agrega un nuevo término al glosario | +| `removeTerm(termId)` | Elimina un término existente | +| `updateTerm(termId, definition, synonyms)` | Actualiza la definición y sinónimos de un término | + +| Excepción lanzada | Condición de disparo | +|------------------------------------------|-------------------------------------------------------| +| `GlossaryTermPlanLimitExceededException` | Se alcanzó el límite `maxGlossaryTermsPerProject` | +| `GlossaryTermNotFoundException` | El término solicitado no existe en el glosario | + +--- + +**Entities** + +**`ProjectConstraint`** — tabla: `project_constraints` + +Restricción técnica o de negocio definido para un proyecto. El `embedding` permite que Requirement Discovery pueda consultarlas semánticamente. + +| Campo | Tipo | Descripción | +|---------------|-------------------------|----------------------------------------------------------| +| `id` | `ProjectConstraintId` | Identificador único de la restricción | +| `projectId` | `ProjectId` | Proyecto al que pertenece | +| `description` | `String` | Descripción textual de la restricción | +| `embedding` | `List?` | Vector embedding para búsqueda semántica | + +| Método | Descripción | +|-----------------------------------|------------------------------------------------------| +| `updateDescription(description)` | Actualiza el texto de la restricción | +| `updateEmbedding(embedding)` | Reemplaza el vector embedding | + +--- + +**`GlossaryTerm`** — tabla: `glossary_terms` + +Término técnico con definición y sinónimos. El `embedding` permite búsqueda semántica desde Requirement Discovery durante la generación de historias de usuario. + +| Campo | Tipo | Descripción | +|--------------|------------------|-----------------------------------------------------------| +| `id` | `GlossaryTermId` | Identificador único del término | +| `glossaryId` | `GlossaryId` | Glosario al que pertenece | +| `term` | `String` | Término técnico o de dominio | +| `definition` | `String` | Definición del término | +| `synonyms` | `List` | Lista de sinónimos o variantes | +| `addedBy` | `UserId` | Usuario que agregó el término | +| `addedAt` | `Instant` | Momento en que fue agregado | +| `embedding` | `List?` | Vector embedding de la definición para búsqueda semántica | + +| Método | Descripción | +|-------------------------------------|------------------------------------------------------| +| `update(definition, synonyms)` | Actualiza la definición y los sinónimos | +| `updateEmbedding(embedding)` | Reemplaza el vector embedding | + +--- + +**Value Objects** + +**`PlanLimits`** — `com.kntrosoft.reqsai.workspace.domain.model.valueobjects` + +Value Object inmutable que encapsula los límites operativos de un plan de facturación. Definido como `record` de Java. + +| Campo | Tipo | Descripción | +|------------------------------|--------|-------------------------------------------------------| +| `maxMembers` | `Int` | Número máximo de miembros en la organización | +| `maxProjects` | `Int` | Número máximo de proyectos en la organización | +| `maxDocumentsPerProject` | `Int` | Número máximo de documentos por proyecto | +| `maxTokensPerMonth` | `Long` | Cuota mensual de tokens de IA | +| `maxGlossaryTermsPerProject` | `Int` | Número máximo de términos de glosario por proyecto | + +--- + +**`TechnicalProfile`** — `com.kntrosoft.reqsai.workspace.domain.model.valueobjects` + +Value Object inmutable que describe el contexto técnico de un proyecto. Utilizado para enriquecer el contexto de la IA durante la elicitación de requisitos. + +| Campo | Tipo | Descripción | +|------------------------|----------------|------------------------------------------------| +| `programmingLanguages` | `List` | Lenguajes de programación usados | +| `frameworks` | `List` | Frameworks y bibliotecas relevantes | +| `architecture` | `String` | Estilo arquitectónico (ej. "microservicios") | +| `domain` | `String` | Dominio del negocio (ej. "fintech", "salud") | + +--- + +**Enumeraciones** + +| Enumeración | Valores | +|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `OrgStatus` | `ACTIVE`, `INACTIVE`, `DELETED` | +| `MemberStatus` | `ACTIVE`, `PENDING`, `INACTIVE` | +| `OrgRole` | `OWNER`, `ADMIN`, `MEMBER` | +| `ProjectStatus` | `ACTIVE`, `ARCHIVED` | +| `DocumentStatus` | `ACTIVE`, `ARCHIVED` | +| `Permission` | `READ_PROJECT`, `WRITE_PROJECT`, `DELETE_PROJECT`, `MANAGE_MEMBERS`, `MANAGE_ROLES`, `UPLOAD_DOCUMENTS`, `MANAGE_GLOSSARY`, `RUN_DISCOVERY`, `MANAGE_INTEGRATIONS` | + +--- + +**Domain Exceptions** + +Todas las excepciones se ubican en `com.kntrosoft.reqsai.workspace.domain.model.exceptions` y extienden `RuntimeException`. + +| Excepción | Mensaje representativo | +|------------------------------------------|----------------------------------------------------------| +| `OrganizationNotFoundException` | `"Organization not found: {id}"` | +| `OrganizationSlugAlreadyExistsException` | `"Slug already in use: {slug}"` | +| `MemberNotFoundException` | `"Member not found: {id}"` | +| `MemberAlreadyExistsException` | `"User is already a member of this organization"` | +| `MemberPlanLimitExceededException` | `"Member limit reached for this plan"` | +| `InsufficientPermissionsException` | `"User does not have required permission: {permission}"` | +| `ProjectNotFoundException` | `"Project not found: {id}"` | +| `ProjectNameAlreadyExistsException` | `"Project name already exists in this organization"` | +| `ProjectPlanLimitExceededException` | `"Project limit reached for this plan"` | +| `ProjectRoleNotFoundException` | `"Project role not found: {id}"` | +| `ProjectRoleNameAlreadyExistsException` | `"Role name already exists in this project"` | +| `ProjectMemberNotFoundException` | `"Project member not found: {id}"` | +| `ProjectMemberAlreadyExistsException` | `"Member is already assigned to this project"` | +| `ProjectDocumentNotFoundException` | `"Document not found: {id}"` | +| `DocumentPlanLimitExceededException` | `"Document limit reached for this project"` | +| `GlossaryNotFoundException` | `"Glossary not found for project: {projectId}"` | +| `GlossaryTermNotFoundException` | `"Glossary term not found: {id}"` | +| `GlossaryTermPlanLimitExceededException` | `"Glossary term limit reached for this project"` | + +--- + +**Domain Events** + +Todos los eventos se ubican en `com.kntrosoft.reqsai.workspace.domain.events`. + +| Evento | Campos principales | Consumidor | +|------------------------------|-------------------------------------------------|-------------------------------------------------| +| `OrganizationCreatedEvent` | `organizationId`, `ownerId`, `planLimits` | Billing BC → `AssignFreeSubscriptionCommand` | +| `MemberInvitedEvent` | `memberId`, `organizationId`, `email`, `role` | Infraestructura → envío de email de invitación | +| `ProjectCreatedEvent` | `projectId`, `organizationId`, `createdBy` | Interno → creación automática de `Glossary` | +| `PlanLimitsUpdatedEvent` | `organizationId`, `newLimits` | Interno → refresco de límites en Organization | + +### 5.3.2. Interface Layer + +**Controladores REST** + +El paquete raíz de la capa de interfaz es `com.kntrosoft.reqsai.workspace.interfaces.rest`. Cada recurso define una interfaz Swagger (`*Controller`) y una implementación (`*ControllerImpl`). Todos los endpoints requieren el header `Authorization: Bearer ` salvo indicación contraria. + +--- + +**`OrganizationController`** — `/api/v1/organizations` + +| Método | Ruta | Descripción | +|----------|------------|-------------------------------------------------------------| +| `POST` | `/` | Crea una nueva organización y el miembro fundador (`OWNER`) | +| `GET` | `/{orgId}` | Obtiene los datos de una organización | +| `PATCH` | `/{orgId}` | Renombra la organización | +| `DELETE` | `/{orgId}` | Elimina lógicamente la organización (estado `DELETED`) | + +--- + +**`MemberController`** — `/api/v1/organizations/{orgId}/members` + +| Método | Ruta | Descripción | +|----------|----------------------|------------------------------------------------------------------| +| `POST` | `/invite` | Invita a un usuario por email; crea `Member` en estado `PENDING` | +| `POST` | `/{memberId}/accept` | El usuario invitado acepta la invitación | +| `GET` | `/` | Lista los miembros de la organización | +| `PATCH` | `/{memberId}/role` | Cambia el rol de un miembro | +| `DELETE` | `/{memberId}` | Desactiva a un miembro de la organización | + +--- + +**`ProjectController`** — `/api/v1/organizations/{orgId}/projects` + +| Método | Ruta | Descripción | +|---------|------------------------|------------------------------------------------| +| `POST` | `/` | Crea un proyecto y su glosario vacío asociado | +| `GET` | `/` | Lista los proyectos de la organización | +| `GET` | `/{projectId}` | Obtiene los datos de un proyecto | +| `PATCH` | `/{projectId}` | Actualiza nombre, descripción y perfil técnico | +| `POST` | `/{projectId}/archive` | Archiva el proyecto | +| `POST` | `/{projectId}/restore` | Reactiva el proyecto archivado | + +--- + +**`ProjectRoleController`** — `/api/v1/projects/{projectId}/roles` + +| Método | Ruta | Descripción | +|----------|---------------|------------------------------------------------| +| `POST` | `/` | Crea un rol personalizado en el proyecto | +| `GET` | `/` | Lista los roles del proyecto | +| `PATCH` | `/{roleId}` | Actualiza nombre y permisos del rol | +| `DELETE` | `/{roleId}` | Elimina un rol del proyecto | + +--- + +**`ProjectMemberController`** — `/api/v1/projects/{projectId}/members` + +| Método | Ruta | Descripción | +|----------|---------------------------|--------------------------------------------------| +| `POST` | `/` | Asigna un miembro de la organización al proyecto | +| `GET` | `/` | Lista los miembros del proyecto | +| `PATCH` | `/{projectMemberId}/role` | Cambia el rol de un miembro dentro del proyecto | +| `DELETE` | `/{projectMemberId}` | Elimina a un miembro del proyecto | + +--- + +**`ProjectDocumentController`** — `/api/v1/projects/{projectId}/documents` + +| Método | Ruta | Descripción | +|--------|--------------------|-------------------------------------------------| +| `POST` | `/` | Registra un documento cargado (URL + metadatos) | +| `GET` | `/` | Lista los documentos del proyecto | +| `POST` | `/{docId}/archive` | Archiva un documento | +| `POST` | `/{docId}/restore` | Reactiva un documento archivado | + +--- + +**`GlossaryController`** — `/api/v1/projects/{projectId}/glossary` + +| Método | Ruta | Descripción | +|----------|-------------------|-----------------------------------------------------------| +| `GET` | `/` | Obtiene el glosario del proyecto con todos sus términos | +| `POST` | `/terms` | Agrega un término al glosario | +| `PATCH` | `/terms/{termId}` | Actualiza la definición y sinónimos de un término | +| `DELETE` | `/terms/{termId}` | Elimina un término del glosario | + +**Request/Response DTOs** + +Los DTO se ubican en `com.kntrosoft.reqsai.workspace.interfaces.rest.dto`. Las anotaciones de validación Jakarta (`@NotBlank`, `@Size`, `@Valid`) se aplican únicamente sobre los request DTO. Las respuestas DTO proyectan los datos necesarios para cada caso de uso. + +| DTO | Tipo | Campos principales | +|-----------------------------|----------|-----------------------------------------------------------------------------------| +| `CreateOrganizationRequest` | Request | `name: String`, `slug: String` | +| `InviteMemberRequest` | Request | `email: String`, `role: OrgRole` | +| `CreateProjectRequest` | Request | `name: String`, `description: String`, `technicalProfile: TechnicalProfileDto` | +| `UpdateProjectRequest` | Request | `name: String?`, `description: String?`, `technicalProfile: TechnicalProfileDto?` | +| `CreateProjectRoleRequest` | Request | `name: String`, `permissions: Set` | +| `AddProjectMemberRequest` | Request | `memberId: String`, `roleId: String` | +| `UploadDocumentRequest` | Request | `name: String`, `url: String`, `mimeType: String` | +| `AddGlossaryTermRequest` | Request | `term: String`, `definition: String`, `synonyms: List` | +| `OrganizationResponse` | Response | `id`, `name`, `slug`, `status`, `planLimits` | +| `ProjectResponse` | Response | `id`, `name`, `description`, `technicalProfile`, `status`, `constraints` | +| `GlossaryResponse` | Response | `id`, `projectId`, `terms: List` | + +### 5.3.3. Application Layer + +**Commands** + +Los comandos se ubican en `com.kntrosoft.reqsai.workspace.application.commands`. + +**Comandos de organización:** + +| Comando | Campos | +|---------------------------------|-----------------------------------------| +| `CreateOrganizationCommand` | `name`, `slug`, `ownerId` | +| `RenameOrganizationCommand` | `organizationId`, `name`, `requestedBy` | +| `DeactivateOrganizationCommand` | `organizationId`, `requestedBy` | +| `DeleteOrganizationCommand` | `organizationId`, `requestedBy` | +| `ApplyPlanLimitsCommand` | `organizationId`, `planLimits` | + +**Comandos de miembro:** + +| Comando | Campos | +|---------------------------|---------------------------------------------------| +| `InviteMemberCommand` | `organizationId`, `email`, `role`, `requestedBy` | +| `AcceptInvitationCommand` | `memberId` | +| `ChangeMemberRoleCommand` | `memberId`, `role`, `requestedBy` | +| `DeactivateMemberCommand` | `memberId`, `requestedBy` | + +**Comandos de proyecto:** + +| Comando | Campos | +|---------------------------------|----------------------------------------------------------------------------| +| `CreateProjectCommand` | `organizationId`, `name`, `description`, `technicalProfile`, `requestedBy` | +| `UpdateProjectCommand` | `projectId`, `name`, `description`, `technicalProfile`, `requestedBy` | +| `UpdateProjectEmbeddingCommand` | `projectId`, `embedding` | +| `ArchiveProjectCommand` | `projectId`, `requestedBy` | +| `RestoreProjectCommand` | `projectId`, `requestedBy` | + +**Comandos de rol de proyecto:** + +| Comando | Campos | +|----------------------------|---------------------------------------------------| +| `CreateProjectRoleCommand` | `projectId`, `name`, `permissions`, `requestedBy` | +| `UpdateProjectRoleCommand` | `roleId`, `name`, `permissions`, `requestedBy` | +| `DeleteProjectRoleCommand` | `roleId`, `requestedBy` | + +**Comandos de miembro de proyecto:** + +| Comando | Campos | +|----------------------------------|-------------------------------------------------| +| `AddProjectMemberCommand` | `projectId`, `memberId`, `roleId`, `assignedBy` | +| `ChangeProjectMemberRoleCommand` | `projectMemberId`, `roleId`, `requestedBy` | +| `RemoveProjectMemberCommand` | `projectMemberId`, `requestedBy` | + +**Comandos de documento:** + +| Comando | Campos | +|---------------------------------|-------------------------------------------------------| +| `UploadProjectDocumentCommand` | `projectId`, `name`, `url`, `mimeType`, `requestedBy` | +| `ArchiveProjectDocumentCommand` | `documentId`, `requestedBy` | +| `RestoreProjectDocumentCommand` | `documentId`, `requestedBy` | + +**Comandos de glosario:** + +| Comando | Campos | +|--------------------------------------|-----------------------------------------------------------| +| `AddGlossaryTermCommand` | `glossaryId`, `term`, `definition`, `synonyms`, `addedBy` | +| `UpdateGlossaryTermCommand` | `termId`, `definition`, `synonyms`, `requestedBy` | +| `RemoveGlossaryTermCommand` | `termId`, `requestedBy` | +| `UpdateGlossaryTermEmbeddingCommand` | `termId`, `embedding` | + +--- + +**Queries** + +Los queries se ubican en `com.kntrosoft.reqsai.workspace.application.queries`. + +| Query | Campos | Descripción | +|--------------------------------|------------------|--------------------------------------------| +| `GetOrganizationQuery` | `organizationId` | Obtiene los datos de una organización | +| `ListOrganizationMembersQuery` | `organizationId` | Lista los miembros de la organización | +| `GetProjectQuery` | `projectId` | Obtiene los datos completos de un proyecto | +| `ListProjectsQuery` | `organizationId` | Lista los proyectos de la organización | +| `GetProjectRolesQuery` | `projectId` | Lista los roles de un proyecto | +| `ListProjectMembersQuery` | `projectId` | Lista los miembros asignados al proyecto | +| `GetGlossaryQuery` | `projectId` | Obtiene el glosario con todos sus términos | +| `ListProjectDocumentsQuery` | `projectId` | Lista los documentos de un proyecto | + +--- + +**Command Handlers** + +Los handlers se ubican en `com.kntrosoft.reqsai.workspace.application.handlers`. Todos son `@Component` con inyección de dependencias y los que modifican estado son `@Transactional`. + +--- + +**`CreateOrganizationCommandHandler`** + +Crea la organización y el miembro fundador en una única transacción, luego publica el evento de creación para que Billing asigne el plan gratuito. + +| Paso | Acción | +|------|-------------------------------------------------------------------------------------------------------| +| 1 | Verifica que el slug no exista; lanza `OrganizationSlugAlreadyExistsException` si falla | +| 2 | Crea `Organization` con `PlanLimits` del plan `FREE` (valores por defecto) | +| 3 | Persiste la organización con `OrganizationRepository` | +| 4 | Crea `Member` para el `ownerId` con `role=OWNER`, `status=ACTIVE`, `invitedBy=null`, `invitedAt=null` | +| 5 | Persiste el miembro con `MemberRepository` | +| 6 | Publica `OrganizationCreatedEvent` | + +--- + +**`InviteMemberCommandHandler`** + +Invita a un nuevo miembro validando que no se supere el límite del plan. + +| Paso | Acción | +|------|--------------------------------------------------------------------------------------------| +| 1 | Recupera la organización; lanza `OrganizationNotFoundException` si no existe | +| 2 | Cuenta miembros activos; lanza `MemberPlanLimitExceededException` si se alcanzó el límite | +| 3 | Verifica que el usuario no sea ya miembro; lanza `MemberAlreadyExistsException` si existe | +| 4 | Crea `Member` con `status=PENDING`, `invitedBy=requestedBy`, `invitedAt=now()` | +| 5 | Persiste el miembro con `MemberRepository` | +| 6 | Publica `MemberInvitedEvent` para disparar el email de invitación | + +--- + +**`CreateProjectCommandHandler`** + +Crea el proyecto validando los límites del plan y luego crea automáticamente su glosario vacío. + +| Paso | Acción | +|------|---------------------------------------------------------------------------------------------| +| 1 | Recupera la organización; lanza `OrganizationNotFoundException` si no existe | +| 2 | Cuenta proyectos activos; lanza `ProjectPlanLimitExceededException` si se alcanzó el límite | +| 3 | Verifica unicidad del nombre en la organización; lanza `ProjectNameAlreadyExistsException` | +| 4 | Crea y persiste el `Project` con `ProjectRepository` | +| 5 | Crea y persiste un `Glossary` vacío asociado al proyecto | +| 6 | Publica `ProjectCreatedEvent` | +| 7 | Despacha `UpdateProjectEmbeddingCommand` (asíncrono) para generar el embedding con IA | + +--- + +**`ApplyPlanLimitsCommandHandler`** + +Escucha los eventos de Billing y actualiza los límites de plan de la organización. + +| Paso | Acción | +|------|-----------------------------------------------------------------------------------------------| +| 1 | Recupera la organización; lanza `OrganizationNotFoundException` si no existe | +| 2 | Llama a `organization.updateLimits(planLimits)` | +| 3 | Persiste los cambios con `OrganizationRepository` | +| 4 | Publica `PlanLimitsUpdatedEvent` | + +--- + +**`UploadProjectDocumentCommandHandler`** + +Registra un documento verificando que no se supere el límite del plan. + +| Paso | Acción | +|------|-----------------------------------------------------------------------------------------------| +| 1 | Recupera el proyecto; lanza `ProjectNotFoundException` si no existe | +| 2 | Cuenta documentos activos; lanza `DocumentPlanLimitExceededException` si se alcanzó el límite | +| 3 | Crea y persiste `ProjectDocument` con `ProjectDocumentRepository` | + +--- + +**`AddGlossaryTermCommandHandler`** + +Agrega un término al glosario verificando el límite del plan y generando el embedding del término. + +| Paso | Acción | +|------|--------------------------------------------------------------------------------------------| +| 1 | Recupera el `Glossary`; lanza `GlossaryNotFoundException` si no existe | +| 2 | Valida el límite de términos; lanza `GlossaryTermPlanLimitExceededException` si se alcanzó | +| 3 | Llama a `glossary.addTerm(term, definition, synonyms, addedBy)` | +| 4 | Persiste el glosario con `GlossaryRepository` | +| 5 | Despacha `UpdateGlossaryTermEmbeddingCommand` (asíncrono) para generar el embedding con IA | + +--- + +**Query Handlers** + +La query handlers son `@Transactional(readOnly = true)` y retornan respuestas DTO directamente desde los repositorios JPA. + +| Query Handler | Descripción | +|---------------------------------------|---------------------------------------------------------------------------| +| `GetOrganizationQueryHandler` | Busca la organización y mapea a `OrganizationResponse` | +| `ListOrganizationMembersQueryHandler` | Lista los miembros activos y mapea a `List` | +| `GetProjectQueryHandler` | Busca el proyecto con sus restricciones y mapea a `ProjectResponse` | +| `ListProjectsQueryHandler` | Lista los proyectos de la organización y mapea a `List` | +| `GetProjectRolesQueryHandler` | Lista los roles del proyecto y mapea a `List` | +| `ListProjectMembersQueryHandler` | Lista los miembros del proyecto y mapea a `List` | +| `GetGlossaryQueryHandler` | Obtiene el glosario con todos sus términos y mapea a `GlossaryResponse` | +| `ListProjectDocumentsQueryHandler` | Lista los documentos activos del proyecto | + +--- + +**Output Ports** + +Los puertos de salida se ubican en `com.kntrosoft.reqsai.workspace.application.ports`. + +**Repository Ports:** + +| Puerto | Método principal | +|------------------------------|--------------------------------------------------------------------------| +| `OrganizationRepository` | `save`, `findById`, `findBySlug`, `countActiveByPlan` | +| `MemberRepository` | `save`, `findById`, `findByOrganizationIdAndUserId`, `countActiveByOrg` | +| `ProjectRepository` | `save`, `findById`, `findByOrganizationId`, `countActiveByOrg` | +| `ProjectRoleRepository` | `save`, `findById`, `findByProjectId`, `existsByProjectIdAndName` | +| `ProjectMemberRepository` | `save`, `findById`, `findByProjectId`, `existsByProjectIdAndMemberId` | +| `ProjectDocumentRepository` | `save`, `findById`, `findByProjectId`, `countActiveByProject` | +| `GlossaryRepository` | `save`, `findById`, `findByProjectId`, `countTermsByGlossary` | + +**Service Ports:** + +| Puerto | Método | Descripción | +|--------------------------------|---------------------------------------------|--------------------------------------------------| +| `EmbeddingServicePort` | `generateEmbedding(text): List` | Genera el vector embedding de un texto usando IA | +| `EmailNotificationServicePort` | `sendInvitationEmail(email, orgName, role)` | Envía el email de invitación a un miembro | + +### 5.3.4. Infrastructure Layer + +**JPA Repositories** + +Los repositorios JPA se ubican en `com.kntrosoft.reqsai.workspace.infrastructure.persistence.jpa` e implementan las interfaces de puerto mediante Spring Data JPA directamente sobre las entidades de dominio. + +| Repositorio JPA | Interfaz de dominio implementada | +|------------------------------------|-------------------------------------| +| `OrganizationJpaRepository` | `OrganizationRepository` | +| `MemberJpaRepository` | `MemberRepository` | +| `ProjectJpaRepository` | `ProjectRepository` | +| `ProjectRoleJpaRepository` | `ProjectRoleRepository` | +| `ProjectMemberJpaRepository` | `ProjectMemberRepository` | +| `ProjectDocumentJpaRepository` | `ProjectDocumentRepository` | +| `GlossaryJpaRepository` | `GlossaryRepository` | + +--- + +**Adapters** + +Los adaptadores se ubican en `com.kntrosoft.reqsai.workspace.infrastructure.adapters`. + +**`OpenAiEmbeddingAdapter`** — implementa `EmbeddingServicePort` + +Genera vectores de embedding usando el modelo `text-embedding-3-small` de OpenAI. Se utiliza para indexar descripciones de proyectos, restricciones técnicas y definiciones de términos del glosario. + +| Método | Descripción | +|---------------------------|-----------------------------------------------------------------------| +| `generateEmbedding(text)` | Invoca la API de OpenAI Embeddings y retorna `List` (1536 dim) | + +**`SmtpEmailNotificationAdapter`** — implementa `EmailNotificationServicePort` + +Envía correos de invitación usando JavaMailSender de Spring. Reutiliza la misma interfaz de puerto definida en IAM BC. + +| Método | Descripción | +|---------------------------------------------|----------------------------------------------------------------| +| `sendInvitationEmail(email, orgName, role)` | Construye y envía el correo de invitación a la organización | + +--- + +**Event Listeners** + +Los listeners se ubican en `com.kntrosoft.reqsai.workspace.infrastructure.events` y están anotados con `@ApplicationModuleListener` para procesamiento asíncrono intermódulo. + +| Listener | Evento escuchado | Acción despachada | +|---------------------------------------|------------------------------|------------------------------------| +| `SubscriptionAssignedEventListener` | `SubscriptionAssignedEvent` | `ApplyPlanLimitsCommand` | +| `SubscriptionUpgradedEventListener` | `SubscriptionUpgradedEvent` | `ApplyPlanLimitsCommand` | + +Ambos listeners extraen el `organizationId` y los nuevos `PlanLimits` del evento de Billing y los despachan al `ApplyPlanLimitsCommandHandler` para actualizar la organización correspondiente. + +### 5.3.6. Bounded Context Software Architecture Component Level Diagrams + +En esta sección se presenta el diagrama de componentes C4 (Nivel 3) del BC Workspace Management. El container es el módulo Spring Modulith completo. Los componentes reflejan la descomposición por capas, incluyendo los handlers de comandos y consultas para organizaciones, miembros, proyectos, roles, documentos y glosarios, así como los listeners de eventos de Billing para actualización de límites de plan. + +![Workspace Component Diagram](assets/diagrams/workspace/workspace-component.png) + +### 5.3.7. Bounded Context Software Architecture Code Level Diagrams + +#### 5.3.7.1. Bounded Context Domain Layer Class Diagrams + +En esta sección se presenta el diagrama de clases UML del Domain Layer del BC Workspace Management. Incluye los siete Aggregate Roots (`Organization`, `Member`, `Project`, `ProjectRole`, `ProjectMember`, `ProjectDocument`, `Glossary`), los Value Objects (`PlanLimits`, `TechnicalProfile` y sus ID correspondientes), las enumeraciones (`OrgStatus`, `OrgRole`, `MemberStatus`, `ProjectStatus`, `DocumentStatus`, `Permission`), los Domain Events y las dieciocho excepciones de dominio organizadas por jerarquía. + +![Workspace Domain Class Diagram](assets/diagrams/workspace/workspace-class.png) + +#### 5.3.7.2. Bounded Context Database Design Diagram + +En esta sección se presenta el diagrama de base de datos del BC Workspace Management. Incluye nueve tablas relacionadas: `organizations`, `members`, `projects`, `project_constraints`, `project_roles`, `project_members`, `project_documents`, `glossaries` y `glossary_terms`. El VO `PlanLimits` se persiste como columnas embebidas `max_*` en `organizations`. El VO `TechnicalProfile` se persiste como columnas embebidas en `projects`. El glosario mantiene una relación 1:1 con el proyecto y se crea automáticamente al crear el proyecto. + +![Workspace Database Diagram](assets/diagrams/workspace/workspace-database.png) + +## 5.4. Bounded Context: Requirement Discovery + +### 5.4.1. Domain Layer + +El Bounded Context de Requirement Discovery es el núcleo de inteligencia de la plataforma. Gestiona las sesiones de elicitación (reuniones transcritas), el procesamiento con IA para extraer historias de usuario y criterios de aceptación, y el flujo de revisión por parte del equipo. Consume contexto de Workspace Management (perfil técnico, restricciones, glosario) para enriquecer la generación automática de artefactos. + +**Aggregate Roots** + +--- + +**`DiscoverySession`** — tabla: `discovery_sessions` + +Representa una sesión de levantamiento de requisitos. Encapsula el ciclo de vida desde la creación de la sesión, la carga del transcript, el procesamiento con IA y la finalización. + +| Campo | Tipo | Descripción | +|-------------------|-----------------------|-------------------------------------------------------------------| +| `id` | `DiscoverySessionId` | Identificador único de la sesión | +| `projectId` | `ProjectId` | Proyecto al que pertenece la sesión | +| `title` | `String` | Título descriptivo de la sesión | +| `transcript` | `String?` | Texto transcrito de la reunión (`null` hasta que se cargue) | +| `status` | `SessionStatus` | Estado del ciclo de vida de la sesión | +| `processingError` | `String?` | Mensaje de error cuando `status = FAILED` | + +| Método | Descripción | +|--------------------------------|-----------------------------------------------------------------| +| `uploadTranscript(transcript)` | Carga el texto del transcript; solo permitido en estado `DRAFT` | +| `startProcessing()` | Cambia el estado a `PROCESSING`; requiere transcript no nulo | +| `complete()` | Cambia el estado a `COMPLETED` tras generación exitosa | +| `fail(error)` | Cambia el estado a `FAILED` y registra el mensaje de error | +| `reset()` | Regresa al estado `DRAFT` para reprocesar | + +| Excepción lanzada | Condición de disparo | +|--------------------------------------|--------------------------------------------------------------| +| `SessionAlreadyProcessingException` | Se intenta iniciar procesamiento en una sesión ya en proceso | +| `TranscriptRequiredException` | Se intenta procesar sin transcript cargado | + +--- + +**`UserStory`** — tabla: `user_stories` + +Historia de usuario generada por IA a partir de una sesión. El equipo la revisa y puede aprobarla, rechazarla o editar su prioridad y puntos de historia. Compone internamente los criterios de aceptación. + +| Campo | Tipo | Descripción | +|----------------------|-----------------------------|-----------------------------------------------------------------| +| `id` | `UserStoryId` | Identificador único de la historia | +| `sessionId` | `DiscoverySessionId` | Sesión de origen | +| `projectId` | `ProjectId` | Proyecto al que pertenece | +| `title` | `String` | Título breve de la historia | +| `role` | `String` | Actor beneficiado (ej. "desarrollador", "administrador") | +| `action` | `String` | Acción que desea realizar el actor | +| `benefit` | `String` | Beneficio esperado de la acción | +| `priority` | `Priority` | Prioridad: `LOW`, `MEDIUM`, `HIGH`, `CRITICAL` | +| `storyPoints` | `Int?` | Estimación de esfuerzo en puntos de historia | +| `status` | `StoryStatus` | Estado: `DRAFT`, `APPROVED`, `REJECTED` | +| `acceptanceCriteria` | `List` | Criterios de aceptación (entidades compuestas) | + +| Método | Descripción | +|------------------------------------|------------------------------------------------------------------| +| `approve()` | Cambia el estado a `APPROVED`; solo desde `DRAFT` | +| `reject()` | Cambia el estado a `REJECTED`; solo desde `DRAFT` | +| `updatePriority(priority)` | Actualiza la prioridad de la historia | +| `updateStoryPoints(points)` | Actualiza los puntos de historia estimados | +| `addCriterion(description, type)` | Agrega un criterio de aceptación | +| `removeCriterion(criterionId)` | Elimina un criterio de aceptación existente | + +| Excepción lanzada | Condición de disparo | +|-------------------------------------|------------------------------------------------------------------| +| `InvalidStoryTransitionException` | Se intenta una transición de estado no válida | + +--- + +**Entities** + +**`AcceptanceCriterion`** — tabla: `acceptance_criteria` + +Criterio de aceptación asociado a una historia de usuario. Puede expresarse en formato Gherkin (`GIVEN_WHEN_THEN`) o como ítem de checklist (`CHECKLIST`). + +| Campo | Tipo | Descripción | +|---------------|-------------------------|-----------------------------------------| +| `id` | `AcceptanceCriterionId` | Identificador único del criterio | +| `storyId` | `UserStoryId` | Historia de usuario a la que pertenece | +| `description` | `String` | Descripción del criterio de aceptación | +| `type` | `CriterionType` | Formato: `GIVEN_WHEN_THEN`, `CHECKLIST` | + +| Método | Descripción | +|-----------------------------|------------------------------------| +| `update(description, type)` | Actualiza la descripción y el tipo | + +--- + +**Value Objects & Enumeraciones** + +| Enumeración | Valores | Descripción | +|-----------------|----------------------------------------------|-------------------------------------------------| +| `SessionStatus` | `DRAFT`, `PROCESSING`, `COMPLETED`, `FAILED` | Ciclo de vida de una sesión de descubrimiento | +| `StoryStatus` | `DRAFT`, `APPROVED`, `REJECTED` | Estado de revisión de una historia de usuario | +| `Priority` | `LOW`, `MEDIUM`, `HIGH`, `CRITICAL` | Prioridad de una historia en el backlog | +| `CriterionType` | `GIVEN_WHEN_THEN`, `CHECKLIST` | Formato de expresión del criterio de aceptación | + +--- + +**Domain Exceptions** + +Todas las excepciones se ubican en `com.kntrosoft.reqsai.discovery.domain.model.exceptions` y extienden `RuntimeException`. + +| Excepción | Mensaje representativo | +|----------------------------------------|-----------------------------------------------------------------| +| `DiscoverySessionNotFoundException` | `"Discovery session not found: {id}"` | +| `SessionAlreadyProcessingException` | `"Session is already being processed"` | +| `TranscriptRequiredException` | `"A transcript must be uploaded before processing can start"` | +| `TokenQuotaExceededException` | `"Monthly token quota has been exceeded for this organization"` | +| `UserStoryNotFoundException` | `"User story not found: {id}"` | +| `AcceptanceCriterionNotFoundException` | `"Acceptance criterion not found: {id}"` | +| `InvalidStoryTransitionException` | `"Cannot transition story from {current} to {target}"` | + +--- + +**Domain Events** + +Todos los eventos se ubican en `com.kntrosoft.reqsai.discovery.domain.events`. + +| Evento | Campos principales | Consumidor | +|---------------------------------|----------------------------------------|------------------------------------------------------------| +| `SessionProcessingStartedEvent` | `sessionId`, `projectId` | Infraestructura → disparo asíncrono de la extracción | +| `UserStoriesGeneratedEvent` | `sessionId`, `projectId`, `storyCount` | Infraestructura → notificación a los miembros del proyecto | +| `AiTokensConsumedEvent` | `organizationId`, `tokensConsumed` | Billing BC → `IncrementTokenUsageCommand` | + +### 5.4.2. Interface Layer + +**Controladores REST** + +El paquete raíz de la capa de interfaz es `com.kntrosoft.reqsai.discovery.interfaces.rest`. Todos los endpoints requieren el header `Authorization: Bearer `. + +--- + +**`DiscoverySessionController`** — `/api/v1/projects/{projectId}/sessions` + +| Método | Ruta | Descripción | +|----------|-------------------------------|---------------------------------------------------------------------------| +| `POST` | `/` | Crea una nueva sesión de descubrimiento en estado `DRAFT` | +| `GET` | `/` | Lista las sesiones del proyecto | +| `GET` | `/{sessionId}` | Obtiene los datos de una sesión | +| `POST` | `/{sessionId}/transcript` | Carga o reemplaza el transcript de la sesión | +| `POST` | `/{sessionId}/process` | Inicia el procesamiento con IA para generar historias de usuario | +| `POST` | `/{sessionId}/reset` | Regresa la sesión a estado `DRAFT` para reprocesar | + +--- + +**`UserStoryController`** — `/api/v1/sessions/{sessionId}/stories` + +| Método | Ruta | Descripción | +|----------|------------------------------|--------------------------------------------------------------------------| +| `GET` | `/` | Lista las historias de usuario de la sesión | +| `GET` | `/{storyId}` | Obtiene una historia de usuario con sus criterios de aceptación | +| `POST` | `/{storyId}/approve` | Aprueba una historia de usuario | +| `POST` | `/{storyId}/reject` | Rechaza una historia de usuario | +| `PATCH` | `/{storyId}/priority` | Actualiza la prioridad de la historia | +| `PATCH` | `/{storyId}/story-points` | Actualiza los puntos de historia estimados | +| `POST` | `/{storyId}/criteria` | Agrega un criterio de aceptación a la historia | +| `PATCH` | `/{storyId}/criteria/{id}` | Actualiza un criterio de aceptación | +| `DELETE` | `/{storyId}/criteria/{id}` | Elimina un criterio de aceptación | + +**Request/Response DTOs** + +Los DTOs se ubican en `com.kntrosoft.reqsai.discovery.interfaces.rest.dto`. + +| DTO | Tipo | Campos principales | +|---------------------------------|----------|-------------------------------------------------------------------------------------------------------| +| `CreateSessionRequest` | Request | `title: String` | +| `UploadTranscriptRequest` | Request | `transcript: String` | +| `UpdatePriorityRequest` | Request | `priority: Priority` | +| `UpdateStoryPointsRequest` | Request | `storyPoints: Int` | +| `AddAcceptanceCriterionRequest` | Request | `description: String`, `type: CriterionType` | +| `DiscoverySessionResponse` | Response | `id`, `projectId`, `title`, `status`, `processingError` | +| `UserStoryResponse` | Response | `id`, `title`, `role`, `action`, `benefit`, `priority`, `storyPoints`, `status`, `acceptanceCriteria` | + +### 5.4.3. Application Layer + +**Commands** + +Los comandos se ubican en `com.kntrosoft.reqsai.discovery.application.commands`. + +| Comando | Campos | +|--------------------------------------|-------------------------------------------------------| +| `CreateDiscoverySessionCommand` | `projectId`, `title`, `requestedBy` | +| `UploadSessionTranscriptCommand` | `sessionId`, `transcript`, `requestedBy` | +| `StartDiscoveryProcessingCommand` | `sessionId`, `requestedBy` | +| `ResetDiscoverySessionCommand` | `sessionId`, `requestedBy` | +| `ApproveUserStoryCommand` | `storyId`, `requestedBy` | +| `RejectUserStoryCommand` | `storyId`, `requestedBy` | +| `UpdateUserStoryPriorityCommand` | `storyId`, `priority`, `requestedBy` | +| `UpdateStoryPointsCommand` | `storyId`, `storyPoints`, `requestedBy` | +| `AddAcceptanceCriterionCommand` | `storyId`, `description`, `type`, `requestedBy` | +| `UpdateAcceptanceCriterionCommand` | `criterionId`, `description`, `type`, `requestedBy` | +| `RemoveAcceptanceCriterionCommand` | `criterionId`, `requestedBy` | + +--- + +**Queries** + +Los queries se ubican en `com.kntrosoft.reqsai.discovery.application.queries`. + +| Query | Campos | Descripción | +|-------------------------------|-----------------|---------------------------------------------------------------------| +| `ListDiscoverySessionsQuery` | `projectId` | Lista todas las sesiones de un proyecto | +| `GetDiscoverySessionQuery` | `sessionId` | Obtiene los datos completos de una sesión | +| `ListUserStoriesQuery` | `sessionId` | Lista las historias de usuario generadas en una sesión | +| `GetUserStoryQuery` | `storyId` | Obtiene una historia con sus criterios de aceptación | + +--- + +**Command Handlers** + +Los handlers se ubican en `com.kntrosoft.reqsai.discovery.application.handlers`. + +--- + +**`StartDiscoveryProcessingCommandHandler`** + +Orquesta el procesamiento con IA: válida el estado de la sesión, enriquece el prompt con contexto del proyecto, invoca la extracción y persiste las historias generadas. + +| Paso | Acción | +|------|------------------------------------------------------------------------------------------------------------| +| 1 | Recupera la sesión; lanza `DiscoverySessionNotFoundException` si no existe | +| 2 | Verifica que `transcript != null`; lanza `TranscriptRequiredException` si está vacío | +| 3 | Llama a `session.startProcessing()`; lanza `SessionAlreadyProcessingException` si aplica | +| 4 | Persiste el estado `PROCESSING` con `DiscoverySessionRepository` | +| 5 | Recupera el contexto del proyecto vía `ProjectContextPort` (perfil técnico, restricciones, glosario) | +| 6 | Invoca `RequirementExtractionPort.extract(transcript, projectContext)`; obtiene `ExtractionResult` | +| 7 | Por cada historia en `ExtractionResult.stories`: crea `UserStory` con sus `AcceptanceCriterion` y persiste | +| 8 | Publica `AiTokensConsumedEvent` con los tokens consumidos según `ExtractionResult.tokensUsed` | +| 9 | Llama a `session.complete()` y persiste el estado final | +| 10 | Publica `UserStoriesGeneratedEvent` | + +Si el paso 6 lanza `TokenQuotaExceededException`: llama a `session.fail("Token quota exceeded")`, persiste y propaga la excepción. + +--- + +**`CreateDiscoverySessionCommandHandler`** + +| Paso | Acción | +|------|--------------------------------------------------------------------------------------------| +| 1 | Crea `DiscoverySession` con `status=DRAFT` y `transcript=null` | +| 2 | Persiste con `DiscoverySessionRepository` | + +--- + +**`ApproveUserStoryCommandHandler`** + +| Paso | Acción | +|------|--------------------------------------------------------------------------------------------| +| 1 | Recupera la historia; lanza `UserStoryNotFoundException` si no existe | +| 2 | Llama a `story.approve()`; lanza `InvalidStoryTransitionException` si no está en `DRAFT` | +| 3 | Persiste con `UserStoryRepository` | + +--- + +**Query Handlers** + +Los query handlers son `@Transactional(readOnly = true)`. + +| Query Handler | Descripción | +|-------------------------------------|------------------------------------------------------------------------------| +| `ListDiscoverySessionsQueryHandler` | Lista las sesiones de un proyecto y mapea a `List` | +| `GetDiscoverySessionQueryHandler` | Obtiene una sesión y mapea a `DiscoverySessionResponse` | +| `ListUserStoriesQueryHandler` | Lista las historias de una sesión y mapea a `List` | +| `GetUserStoryQueryHandler` | Obtiene una historia con sus criterios y mapea a `UserStoryResponse` | + +--- + +**Output Ports** + +Los puertos de salida se ubican en `com.kntrosoft.reqsai.discovery.application.ports`. + +**Repository Ports:** + +| Puerto | Métodos principales | +|------------------------------|---------------------------------------------------------------------| +| `DiscoverySessionRepository` | `save`, `findById`, `findByProjectId` | +| `UserStoryRepository` | `save`, `findById`, `findBySessionId`, `saveAll` | + +**Service Ports:** + +| Puerto | Método | Descripción | +|-----------------------------|--------------------------------------------------|---------------------------------------------------------------------| +| `RequirementExtractionPort` | `extract(transcript, context): ExtractionResult` | Envía el transcript y contexto a la IA y retorna historias + tokens | +| `ProjectContextPort` | `getContext(projectId): ProjectContext` | Obtiene perfil técnico, restricciones y términos del glosario | + +`ExtractionResult` es un record con `stories: List` y `tokensUsed: Long`. `GeneratedStory` contiene título, rol, acción, beneficio y criterios de aceptación en texto plano. `ProjectContext` es un record con `TechnicalProfile`, `List` de restricciones y `Map` de términos del glosario (término → definición). + +### 5.4.4. Infrastructure Layer + +**JPA Repositories** + +Los repositorios JPA se ubican en `com.kntrosoft.reqsai.discovery.infrastructure.persistence.jpa`. + +| Repositorio JPA | Interfaz de dominio implementada | +|-----------------------------------|-----------------------------------| +| `DiscoverySessionJpaRepository` | `DiscoverySessionRepository` | +| `UserStoryJpaRepository` | `UserStoryRepository` | + +--- + +**Adapters** + +Los adaptadores se ubican en `com.kntrosoft.reqsai.discovery.infrastructure.adapters`. + +**`OpenAiRequirementExtractionAdapter`** — implementa `RequirementExtractionPort` + +Invoca la API de OpenAI (modelo GPT-4o) con un prompt estructurado que incluye el transcript y el contexto del proyecto. El prompt instruye al modelo para que devuelva las historias en formato JSON estructurado con título, rol, acción, beneficio y criterios de aceptación. + +| Método | Descripción | +|--------------------------------|--------------------------------------------------------------------------------| +| `extract(transcript, context)` | Construye el prompt, invoca OpenAI Chat Completions y parsea el JSON retornado | + +La respuesta JSON del modelo se parsea a `ExtractionResult`. Los tokens utilizados se extraen del campo `usage.total_tokens` de la respuesta de la API. + +**`WorkspaceContextAdapter`** — implementa `ProjectContextPort` + +Consulta directamente los repositorios JPA de Workspace Management para obtener el contexto del proyecto. Al ser módulos del mismo servicio Spring, el acceso es directo sin llamada HTTP. + +| Método | Descripción | +|-------------------------|------------------------------------------------------------------------------------| +| `getContext(projectId)` | Obtiene `TechnicalProfile` del proyecto, sus restricciones y términos del glosario | + +--- + +**Event Listeners** + +Los listeners se ubican en `com.kntrosoft.reqsai.discovery.infrastructure.events` y están anotados con `@ApplicationModuleListener`. + +| Listener | Evento escuchado | Acción | +|-----------------------------------|---------------------------|---------------------------------------------------------------------------------------------------------| +| `TokenQuotaExceededEventListener` | `TokenQuotaExceededEvent` | Falla todas las sesiones en estado `PROCESSING` de la organización con mensaje `"Token quota exceeded"` | +| `AiTokensConsumedEventListener` | `AiTokensConsumedEvent` | Despacha `IncrementTokenUsageCommand` al handler de Billing BC | + +### 5.4.6. Bounded Context Software Architecture Component Level Diagrams + +En esta sección se presenta el diagrama de componentes C4 (Nivel 3) del BC Requirement Discovery. El container es el módulo Spring Modulith completo. Los componentes reflejan la descomposición por capas, incluyendo el pipeline de procesamiento con IA (transcripción → generación de historias → criterios de aceptación), los ports de integración con el LLM/STT y los listeners de eventos de Billing para control de cuota de tokens. + +![Discovery Component Diagram](assets/diagrams/discovery/discovery-component.png) + +### 5.4.7. Bounded Context Software Architecture Code Level Diagrams + +#### 5.4.7.1. Bounded Context Domain Layer Class Diagrams + +En esta sección se presenta el diagrama de clases UML del Domain Layer del BC Requirement Discovery. Incluye los dos Aggregate Roots (`DiscoverySession`, `UserStory`), la entidad `AcceptanceCriterion` (pertenece a `UserStory`), los Value Objects de identidad, las enumeraciones (`SessionStatus`, `StoryStatus`, `Priority`, `CriterionType`), los Domain Events internos y el evento `AiTokensConsumedEvent` publicado al Api package para que Billing BC actualice el consumo de tokens. + +![Discovery Domain Class Diagram](assets/diagrams/discovery/discovery-class.png) + +#### 5.4.7.2. Bounded Context Database Design Diagram + +En esta sección se presenta el diagrama de base de datos del BC Requirement Discovery. Incluye tres tablas relacionadas: `discovery_sessions`, `user_stories` y `acceptance_criteria`. La tabla `project_id` en `discovery_sessions` es una referencia lógica al BC Workspace (sin FK física, BC aislados). Los criterios de aceptación se almacenan como filas en su propia tabla con su tipo (`GIVEN_WHEN_THEN` o `CHECKLIST`), en lugar de JSON embebido. + +![Discovery Database Diagram](assets/diagrams/discovery/discovery-database.png) + +## 5.5. Bounded Context: Integration Gateway + +### 5.5.1. Domain Layer + +El Bounded Context de Integration Gateway gestiona las integraciones de Reqs-AI con herramientas externas de gestión de proyectos, principalmente Jira. Permite exportar las historias de usuario aprobadas como issues al backlog del equipo y sincronizar el estado de dichos issues de vuelta a la plataforma. Este BC actúa como anticorruption layer entre el dominio de Reqs-AI y los modelos de datos de herramientas externas. + +**Aggregate Roots** + +--- + +**`Integration`** — tabla: `integrations` + +Representa la conexión configurada entre un proyecto de Reqs-AI y una herramienta externa. Almacena las credenciales de acceso de forma encriptada y el mapeo de configuración necesario para la exportación. + +| Campo | Tipo | Descripción | +|------------------|-----------------------|-----------------------------------------------------------| +| `id` | `IntegrationId` | Identificador único de la integración | +| `projectId` | `ProjectId` | Proyecto de Reqs-AI asociado | +| `provider` | `IntegrationProvider` | Proveedor externo: `JIRA`, `TRELLO`, `LINEAR` | +| `status` | `IntegrationStatus` | Estado: `ACTIVE`, `INACTIVE`, `ERROR` | +| `config` | `IntegrationConfig` | Configuración de conexión (URL, proyecto destino, mapeos) | +| `encryptedToken` | `String` | Token de acceso OAuth encriptado con AES | + +| Método | Descripción | +|-------------------------------|-------------------------------------------------| +| `activate()` | Cambia el estado a `ACTIVE` | +| `deactivate()` | Cambia el estado a `INACTIVE` | +| `markError(reason)` | Cambia el estado a `ERROR` y registra el motivo | +| `updateConfig(config)` | Actualiza la configuración de conexión | +| `rotateToken(encryptedToken)` | Rota el token de acceso almacenado | + +| Excepción lanzada | Condición de disparo | +|-------------------------------------|---------------------------------------------------------------| +| `IntegrationAlreadyExistsException` | Ya existe una integración activa para el proveedor y proyecto | +| `IntegrationInactiveException` | Se intenta exportar con una integración desactivada | + +--- + +**`ExportRecord`** — tabla: `export_records` + +Registra cada exportación de una historia de usuario hacia la herramienta externa. Mantiene la referencia al issue externo creado para permitir sincronización posterior. + +| Campo | Tipo | Descripción | +|-------------------|------------------|-----------------------------------------------------------------| +| `id` | `ExportRecordId` | Identificador único del registro de exportación | +| `integrationId` | `IntegrationId` | Integración usada para la exportación | +| `storyId` | `UserStoryId` | Historia de usuario exportada | +| `externalIssueId` | `String` | ID del issue en la herramienta externa (ej. `PROJ-123` en Jira) | +| `externalUrl` | `String` | URL pública del issue externo | +| `status` | `ExportStatus` | Estado: `PENDING`, `EXPORTED`, `SYNCED`, `FAILED` | +| `failureReason` | `String?` | Motivo del fallo cuando `status = FAILED` | + +| Método | Descripción | +|------------------------------|------------------------------------------------------------| +| `markExported(issueId, url)` | Registra el ID y URL del issue creado, cambia a `EXPORTED` | +| `markSynced()` | Cambia el estado a `SYNCED` tras sincronización exitosa | +| `markFailed(reason)` | Cambia el estado a `FAILED` y registra el motivo | + +| Excepción lanzada | Condición de disparo | +|------------------------------------|----------------------------------------------------------------| +| `StoryAlreadyExportedException` | La historia ya tiene un registro de exportación exitoso | + +--- + +**Value Objects & Enumeraciones** + +**`IntegrationConfig`** — `com.kntrosoft.reqsai.gateway.domain.model.valueobjects` + +Value Object inmutable que encapsula la configuración específica de cada integración. + +| Campo | Tipo | Descripción | +|--------------------|-----------------------|--------------------------------------------------------------------------| +| `baseUrl` | `String` | URL base de la instancia del proveedor (ej. `https://org.atlassian.net`) | +| `projectKey` | `String` | Clave del proyecto destino en la herramienta externa | +| `issueTypeMapping` | `Map` | Mapeo de tipos de historia a tipos de issue del proveedor | + +| Enumeración | Valores | Descripción | +|-----------------------|-------------------------------------------|---------------------------------------| +| `IntegrationProvider` | `JIRA`, `TRELLO`, `LINEAR` | Proveedores de integración soportados | +| `IntegrationStatus` | `ACTIVE`, `INACTIVE`, `ERROR` | Estado operativo de la integración | +| `ExportStatus` | `PENDING`, `EXPORTED`, `SYNCED`, `FAILED` | Estado del ciclo de exportación | + +--- + +**Domain Exceptions** + +Todas las excepciones se ubican en `com.kntrosoft.reqsai.gateway.domain.model.exceptions` y extienden `RuntimeException`. + +| Excepción | Mensaje representativo | +|-------------------------------------|-----------------------------------------------------------------------------------------| +| `IntegrationNotFoundException` | `"Integration not found: {id}"` | +| `IntegrationAlreadyExistsException` | `"An active integration already exists for provider {provider} in project {projectId}"` | +| `IntegrationInactiveException` | `"Integration is not active"` | +| `ExportRecordNotFoundException` | `"Export record not found: {id}"` | +| `StoryAlreadyExportedException` | `"Story {storyId} has already been exported"` | +| `ExternalProviderException` | `"External provider returned error: {message}"` | + +--- + +**Domain Events** + +Todos los eventos se ubican en `com.kntrosoft.reqsai.gateway.domain.events`. + +| Evento | Campos principales | Consumidor | +|-----------------------------|--------------------------------------------------|----------------------------------------------------| +| `StoryExportedEvent` | `exportRecordId`, `storyId`, `externalIssueId` | Infraestructura → notificación al usuario | +| `ExportFailedEvent` | `exportRecordId`, `storyId`, `reason` | Infraestructura → alerta al usuario del fallo | + +### 5.5.2. Interface Layer + +**Controladores REST** + +El paquete raíz de la capa de interfaz es `com.kntrosoft.reqsai.gateway.interfaces.rest`. Todos los endpoints requieren el header `Authorization: Bearer `. + +--- + +**`IntegrationController`** — `/api/v1/projects/{projectId}/integrations` + +| Método | Ruta | Descripción | +|----------|-------------------------------|--------------------------------------------------------------| +| `POST` | `/` | Configura una nueva integración para el proyecto | +| `GET` | `/` | Lista las integraciones del proyecto | +| `GET` | `/{integrationId}` | Obtiene los datos de una integración | +| `PATCH` | `/{integrationId}` | Actualiza la configuración o rota el token de la integración | +| `POST` | `/{integrationId}/activate` | Activa una integración desactivada | +| `POST` | `/{integrationId}/deactivate` | Desactiva una integración activa | +| `DELETE` | `/{integrationId}` | Elimina la integración | + +--- + +**`ExportController`** — `/api/v1/integrations/{integrationId}/exports` + +| Método | Ruta | Descripción | +|----------|------------------------|--------------------------------------------------------------------------| +| `POST` | `/` | Exporta una o varias historias aprobadas al proveedor externo | +| `GET` | `/` | Lista los registros de exportación de la integración | +| `GET` | `/{exportId}` | Obtiene los detalles de un registro de exportación | +| `POST` | `/{exportId}/sync` | Sincroniza el estado del issue externo con el registro local | + +--- + +**`WebhookController`** — `/api/v1/webhooks/integrations` + +Recibe notificaciones entrantes de los proveedores externos (ej. cambio de estado de un issue en Jira). No requiere autenticación Bearer; válida la firma del webhook del proveedor. + +| Método | Ruta | Descripción | +|--------|---------|-------------------------------------------------------------------------------------| +| `POST` | `/jira` | Recibe eventos de Jira (issue updated, issue deleted) y actualiza el `ExportRecord` | + +**Request/Response DTOs** + +Los DTOs se ubican en `com.kntrosoft.reqsai.gateway.interfaces.rest.dto`. + +| DTO | Tipo | Campos principales | +|----------------------------|----------|----------------------------------------------------------------------------------| +| `CreateIntegrationRequest` | Request | `provider: IntegrationProvider`, `config: IntegrationConfigDto`, `token: String` | +| `UpdateIntegrationRequest` | Request | `config: IntegrationConfigDto?`, `token: String?` | +| `ExportStoriesRequest` | Request | `storyIds: List` | +| `IntegrationResponse` | Response | `id`, `projectId`, `provider`, `status`, `config` | +| `ExportRecordResponse` | Response | `id`, `storyId`, `externalIssueId`, `externalUrl`, `status` | + +### 5.5.3. Application Layer + +**Commands** + +Los comandos se ubican en `com.kntrosoft.reqsai.gateway.application.commands`. + +| Comando | Campos | +|----------------------------------|--------------------------------------------------------------------| +| `CreateIntegrationCommand` | `projectId`, `provider`, `config`, `encryptedToken`, `requestedBy` | +| `UpdateIntegrationCommand` | `integrationId`, `config?`, `encryptedToken?`, `requestedBy` | +| `ActivateIntegrationCommand` | `integrationId`, `requestedBy` | +| `DeactivateIntegrationCommand` | `integrationId`, `requestedBy` | +| `DeleteIntegrationCommand` | `integrationId`, `requestedBy` | +| `ExportStoriesToProviderCommand` | `integrationId`, `storyIds`, `requestedBy` | +| `SyncExportRecordCommand` | `exportRecordId` | + +--- + +**Queries** + +Los queries se ubican en `com.kntrosoft.reqsai.gateway.application.queries`. + +| Query | Campos | Descripción | +|--------------------------|------------------|-------------------------------------------------------| +| `ListIntegrationsQuery` | `projectId` | Lista las integraciones de un proyecto | +| `GetIntegrationQuery` | `integrationId` | Obtiene los datos de una integración | +| `ListExportRecordsQuery` | `integrationId` | Lista los registros de exportación de una integración | +| `GetExportRecordQuery` | `exportRecordId` | Obtiene los detalles de un registro de exportación | + +--- + +**Command Handlers** + +Los handlers se ubican en `com.kntrosoft.reqsai.gateway.application.handlers`. + +--- + +**`CreateIntegrationCommandHandler`** + +| Paso | Acción | +|------|--------------------------------------------------------------------------------------------------------------| +| 1 | Verifica que no exista una integración activa del mismo proveedor; lanza `IntegrationAlreadyExistsException` | +| 2 | Encripta el token con `TokenEncryptionPort.encrypt(token)` | +| 3 | Crea y persiste `Integration` con `status=ACTIVE` | + +--- + +**`ExportStoriesToProviderCommandHandler`** + +Orquesta la exportación de un conjunto de historias aprobadas al proveedor externo. Cada historia genera un `ExportRecord` individual. + +| Paso | Acción | +|------|--------------------------------------------------------------------------------------------------------------------| +| 1 | Recupera la integración; lanza `IntegrationNotFoundException` si no existe | +| 2 | Verifica `status=ACTIVE`; lanza `IntegrationInactiveException` si aplica | +| 3 | Desencripta el token con `TokenEncryptionPort.decrypt(encryptedToken)` | +| 4 | Por cada `storyId`: verifica que no exista `ExportRecord` exitoso; lanza `StoryAlreadyExportedException` si aplica | +| 5 | Recupera los datos de la historia vía `UserStoryPort.getStory(storyId)` | +| 6 | Invoca `ExternalProviderPort.createIssue(story, integration)` para crear el issue | +| 7 | Crea y persiste `ExportRecord` con `status=EXPORTED` y el `externalIssueId` retornado | +| 8 | Publica `StoryExportedEvent` | + +Si el paso 6 lanza error: persiste `ExportRecord` con `status=FAILED` y pública `ExportFailedEvent`. + +--- + +**`SyncExportRecordCommandHandler`** + +| Paso | Acción | +|------|--------------------------------------------------------------------------------------------------| +| 1 | Recupera el `ExportRecord`; lanza `ExportRecordNotFoundException` si no existe | +| 2 | Invoca `ExternalProviderPort.getIssueStatus(externalIssueId, integration)` | +| 3 | Actualiza el estado del `ExportRecord` a `SYNCED` | + +--- + +**Query Handlers** + +Los query handlers son `@Transactional(readOnly = true)`. + +| Query Handler | Descripción | +|---------------------------------|----------------------------------------------------------------------------| +| `ListIntegrationsQueryHandler` | Lista las integraciones del proyecto y mapea a `List` | +| `GetIntegrationQueryHandler` | Obtiene una integración y mapea a `IntegrationResponse` | +| `ListExportRecordsQueryHandler` | Lista los registros de exportación y mapea a `List` | +| `GetExportRecordQueryHandler` | Obtiene un registro y mapea a `ExportRecordResponse` | + +--- + +**Output Ports** + +Los puertos de salida se ubican en `com.kntrosoft.reqsai.gateway.application.ports`. + +**Repository Ports:** + +| Puerto | Métodos principales | +|--------------------------|------------------------------------------------------------------------| +| `IntegrationRepository` | `save`, `findById`, `findByProjectId`, `existsByProjectAndProvider` | +| `ExportRecordRepository` | `save`, `findById`, `findByIntegrationId`, `existsByStoryIdAndSuccess` | + +**Service Ports:** + +| Puerto | Método | Descripción | +|------------------------|------------------------------------------------|-----------------------------------------------------------------| +| `ExternalProviderPort` | `createIssue(story, integration): String` | Crea un issue en el proveedor externo y retorna el ID externo | +| `ExternalProviderPort` | `getIssueStatus(issueId, integration): String` | Consulta el estado actual de un issue en el proveedor | +| `TokenEncryptionPort` | `encrypt(token): String` | Encripta el token de acceso con AES antes de persistir | +| `TokenEncryptionPort` | `decrypt(encryptedToken): String` | Desencripta el token para usarlo en llamadas al proveedor | +| `UserStoryPort` | `getStory(storyId): StoryDto` | Obtiene los datos de una historia de usuario desde Discovery BC | + +### 5.5.4. Infrastructure Layer + +**JPA Repositories** + +Los repositorios JPA se ubican en `com.kntrosoft.reqsai.gateway.infrastructure.persistence.jpa`. + +| Repositorio JPA | Interfaz de dominio implementada | +|--------------------------------|----------------------------------| +| `IntegrationJpaRepository` | `IntegrationRepository` | +| `ExportRecordJpaRepository` | `ExportRecordRepository` | + +--- + +**Adapters** + +Los adaptadores se ubican en `com.kntrosoft.reqsai.gateway.infrastructure.adapters`. + +**`JiraProviderAdapter`** — implementa `ExternalProviderPort` para `provider=JIRA` + +Utiliza la API REST de Jira Cloud para crear issues y consultar su estado. El anticorruption layer traduce el modelo de `UserStory` de Reqs-AI al modelo de issue de Jira. + +| Método | Descripción | +|----------------------------------------|-----------------------------------------------------------------------------| +| `createIssue(story, integration)` | Mapea la historia al formato de Jira y llama a `POST /rest/api/3/issue` | +| `getIssueStatus(issueId, integration)` | Consulta `GET /rest/api/3/issue/{issueId}` y retorna el campo `status.name` | + +**`AesTokenEncryptionAdapter`** — implementa `TokenEncryptionPort` + +Encripta y desencripta tokens OAuth usando AES-256-GCM. La clave de encriptación se inyecta desde configuración de entorno (`ENCRYPTION_KEY`). + +| Método | Descripción | +|---------------------------|----------------------------------------------------------------| +| `encrypt(token)` | Encripta el token y retorna el ciphertext en Base64 | +| `decrypt(encryptedToken)` | Desencripta el ciphertext y retorna el token original | + +**`DiscoveryUserStoryAdapter`** — implementa `UserStoryPort` + +Accede directamente a los repositorios JPA de Requirement Discovery para obtener los datos de una historia sin llamada HTTP, aprovechando la co-ubicación en el mismo servicio Spring. + +| Método | Descripción | +|-------------------------|----------------------------------------------------------------------| +| `getStory(storyId)` | Busca la historia y sus criterios de aceptación y mapea a `StoryDto` | + +--- + +**Configuración de infraestructura:** + +| Clase | Propósito | +|------------------------------|-----------------------------------------------------------------------------------------------------| +| `JiraWebhookSecurityFilter` | Valida la firma HMAC-SHA256 del header `X-Hub-Signature` en las notificaciones entrantes de Jira | +| `IntegrationProviderFactory` | Fábrica que retorna el `ExternalProviderPort` correcto según el `IntegrationProvider` del aggregate | + +### 5.5.6. Bounded Context Software Architecture Component Level Diagrams + +En esta sección se presenta el diagrama de componentes C4 (Nivel 3) del BC Integration Gateway. El container es el módulo Spring Modulith completo. Los componentes reflejan la descomposición por capas, incluyendo los handlers de comandos para crear y gestionar integraciones y exportaciones, el `IntegrationProviderFactory` que abstrae los proveedores externos (Jira, Trello, Linear) y los adapters de infraestructura para comunicación con las API externas. + +![Gateway Component Diagram](assets/diagrams/gateway/gateway-component.png) + +### 5.5.7. Bounded Context Software Architecture Code Level Diagrams + +#### 5.5.7.1. Bounded Context Domain Layer Class Diagrams + +En esta sección se presenta el diagrama de clases UML del Domain Layer del BC Integration Gateway. Incluye los dos Aggregate Roots (`Integration`, `ExportRecord`), el Value Object `IntegrationConfig` (con `baseUrl`, `projectKey` e `issueTypeMapping`), las enumeraciones (`IntegrationProvider` con JIRA/TRELLO/LINEAR, `IntegrationStatus`, `ExportStatus`), los Domain Events y las excepciones de dominio. El BC es agnóstico al proveedor concreto gracias al patrón Strategy implementado via `IntegrationProvider`. + +![Gateway Domain Class Diagram](assets/diagrams/gateway/gateway-class.png) + +#### 5.5.7.2. Bounded Context Database Design Diagram + +En esta sección se presenta el diagrama de base de datos del BC Integration Gateway. Incluye dos tablas: `integrations` y `export_records`. El VO `IntegrationConfig` se persiste como columnas embebidas (`base_url`, `project_key`, `issue_type_mapping`). El token de autenticación se almacena cifrado en reposo (AES-256) en la columna `encrypted_token`. Las referencias a proyectos (Workspace BC) y a historias (Discovery BC) son lógicas, sin FK físicas entre BC. + +![Gateway Database Diagram](assets/diagrams/gateway/gateway-database.png) + +# Capítulo VI: Solution UX Design + +## 6.1. Style Guidelines + +En esta sección se definen los lineamientos visuales y de experiencia que guían el diseño de Reqs-AI. El objetivo principal es mantener una interfaz clara, consistente y profesional, alineada con el propósito del producto: ayudar a equipos de software a convertir reuniones de levantamiento de requisitos en historias de usuario estructuradas, criterios de aceptación y artefactos listos para el backlog. + +El diseño de la aplicación sigue una estética limpia, similar a herramientas de productividad como Notion, priorizando espacios en blanco, jerarquía visual clara, navegación lateral persistente y componentes reutilizables. Esta decisión permite que el usuario se concentre en la revisión de proyectos, sesiones, historias generadas por IA e integraciones sin distraerse con elementos visuales innecesarios. + +Reqs-AI Component Style Examples + +### 6.1.1. General Style Guidelines + +Los lineamientos generales de estilo de Reqs-AI se enfocan en transmitir precisión, confianza y productividad. Al tratarse de una plataforma SaaS orientada a requisitos de software, la interfaz debe comunicar orden, seguridad y facilidad de uso. Por ello, se utiliza una composición minimalista, con tarjetas limpias, bordes suaves, sombras ligeras y una estructura modular que permite reconocer rápidamente cada sección del sistema. + +La paleta visual se apoya principalmente en tonos neutros, fondos claros y un azul principal para acciones relevantes. El azul se utiliza en botones primarios, enlaces, estados activos y elementos de navegación seleccionados. Los tonos grises se reservan para contenedores, bordes, textos secundarios y fondos de separación. Además, se incorporan colores semánticos para comunicar estados específicos: verde para acciones completadas, amarillo para advertencias, rojo para errores y un acento violeta o azul intenso para elementos relacionados con inteligencia artificial. + +Reqs-AI Color Palette + +A nivel tipográfico, se propone el uso de una familia sans serif moderna y legible para interfaces digitales. Los títulos deben tener mayor peso visual para diferenciar secciones principales, mientras que los textos descriptivos deben mantener un tamaño cómodo para lectura en pantallas. La jerarquía recomendada distingue entre títulos de página, subtítulos, nombres de módulos, cuerpo de texto, etiquetas, metadatos y mensajes de ayuda. + +Reqs-AI Typography Scale + +Los componentes siguen criterios de consistencia. Los botones principales se reservan para acciones críticas como crear workspace, iniciar sesión en vivo, aprobar historia o conectar Jira. Los botones secundarios se utilizan para editar, cancelar, visualizar detalles o navegar entre estados. Los formularios deben mostrar etiquetas claras, placeholders descriptivos y mensajes de validación visibles cuando exista un error. Las tarjetas se utilizan para agrupar información de proyectos, sesiones, historias y métricas de uso. + +En cuanto a accesibilidad, se prioriza el contraste entre texto y fondo, el uso de etiquetas comprensibles, la persistencia de navegación global y la diferenciación visual de estados. Los elementos interactivos deben mantener tamaños adecuados para facilitar su uso en escritorio y pantallas reducidas. Asimismo, los mensajes de error deben ser específicos para que el usuario pueda corregir su acción sin depender de interpretación técnica. + +### 6.1.2. Web, Mobile & Devices Style Guidelines + +Para la web application, Reqs-AI utiliza una estructura de aplicación empresarial basada en tres zonas principales: sidebar lateral, topbar superior y área central de contenido. La sidebar permite acceder a módulos como Dashboard, Projects, Sessions, User Stories, Integrations, Billing y Settings. La topbar concentra el selector de workspace, la búsqueda global, acciones rápidas y el menú de perfil. El área central se adapta al módulo activo mediante tablas, tarjetas, modales y paneles laterales. + +Reqs-AI Navigation System + +En escritorio, la aplicación debe aprovechar el ancho disponible para mostrar información densa, como listados de sesiones, tableros de historias, métricas de consumo, configuración de integraciones y paneles de revisión. En estos casos, se recomienda usar layouts de dos columnas o paneles laterales para evitar abrir demasiadas pantallas nuevas. Por ejemplo, una historia de usuario puede listarse en el tablero y abrirse en un drawer lateral para revisar criterios Gherkin, confianza de IA y acciones de aprobación. + +En dispositivos móviles, la experiencia debe simplificarse priorizando consultas rápidas, revisión de estados y acciones puntuales. La navegación lateral puede transformarse en menú colapsable, la topbar debe reducirse a búsqueda, perfil y acción principal, y las tablas deben convertirse en tarjetas verticales. Esto permite mantener la coherencia visual sin sobrecargar pantallas pequeñas. + +Para dispositivos externos o escenarios de grabación, la interfaz debe reforzar estados claros: sesión activa, sesión pausada, procesamiento en curso, historia generada, historia pendiente de revisión y exportación completada. Estos estados son importantes porque Reqs-AI trabaja con procesos en vivo y procesamiento asistido por IA, por lo que el usuario necesita retroalimentación inmediata para confiar en el sistema. + +## 6.2. Information Architecture + +La arquitectura de información de Reqs-AI organiza los contenidos de la plataforma en función del flujo real del usuario: acceder a la cuenta, crear o seleccionar un workspace, configurar proyectos, iniciar sesiones de descubrimiento, revisar historias generadas por IA, exportarlas hacia herramientas externas y administrar la organización. Esta organización permite que la experiencia sea coherente con el proceso de levantamiento de requisitos y no solo con una agrupación técnica de funcionalidades. + +La estructura principal se divide en módulos globales: Authentication, Workspace, Dashboard, Projects, Discovery Sessions, User Stories, Integrations, Billing y Settings. Cada módulo agrupa acciones relacionadas y mantiene una nomenclatura consistente para reducir ambigüedad. De esta forma, el usuario puede comprender dónde iniciar una tarea, dónde revisar resultados y dónde configurar aspectos administrativos. + +Reqs-AI Information Architecture Map + +### 6.2.2. Labeling Systems + +El sistema de etiquetado de Reqs-AI utiliza términos breves, consistentes y orientados al lenguaje del dominio. Las etiquetas principales se mantienen en inglés porque el producto trabaja con conceptos habituales del entorno SaaS y de gestión ágil, como Workspace, Projects, Sessions, User Stories, Integrations y Billing. Sin embargo, las descripciones internas pueden adaptarse al idioma del usuario cuando se requiera internacionalización. + +Las etiquetas de navegación deben indicar claramente el contenido del módulo. “Projects” agrupa iniciativas por cliente o producto; “Sessions” representa reuniones procesadas o en vivo; “User Stories” contiene historias generadas por IA; “Integrations” centraliza conexiones con Jira u otras herramientas; “Billing” administra plan y consumo; y “Settings” concentra la configuración organizacional y de equipo. + +Reqs-AI Labeling System + +Para las acciones, se recomienda utilizar verbos directos que indiquen intención: Create Workspace, Start Live Session, Create Project, Approve Story, Export to Jira, Invite Member y Connect Integration. Para los estados, se deben usar etiquetas que permitan comprender el avance del flujo: Draft, Active, Processing, Pending Review, Approved, Exported, Failed y Archived. + +Este sistema de etiquetado ayuda a reducir la carga cognitiva porque el usuario puede asociar rápidamente cada término con una tarea concreta del proceso de levantamiento, revisión y transferencia de requisitos. + +### 6.2.3. Searching Systems + +El sistema de búsqueda de Reqs-AI debe facilitar la localización rápida de proyectos, sesiones, historias de usuario, miembros e integraciones. La búsqueda principal se ubica en la topbar para estar disponible desde cualquier módulo de la aplicación. Esto permite que el usuario no dependa únicamente de la navegación lateral cuando necesita encontrar información específica. + +Reqs-AI Searching System + +La búsqueda global debe aceptar palabras clave relacionadas con nombres de proyectos, títulos de sesiones, identificadores de historias, estados de revisión, miembros del equipo y referencias externas como tickets de Jira. Además, cada módulo debe contar con filtros contextuales. En Projects, los filtros pueden ser estados, cliente o fecha de actualización. En Sessions, los filtros pueden ser proyecto, estado de procesamiento o fecha. En User Stories, los filtros pueden ser estados, prioridad, confianza de IA, similitud detectada o exportación. + +Para mejorar la experiencia, los resultados deben mostrar información resumida: nombre del elemento, tipo, estado, fecha de actualización y acción rápida. Esto permite que el usuario decida si desea abrir el detalle, editar, aprobar o exportar sin ingresar a múltiples pantallas. + +### 6.2.4. SEO Tags and Meta Tags + +Los SEO Tags y Meta Tags se aplican principalmente a la landing page pública de Reqs-AI, ya que la aplicación interna se encuentra protegida por autenticación y no debe indexarse en buscadores. La landing page debe estar optimizada para comunicar la propuesta de valor del producto, captar leads y explicar cómo la plataforma convierte reuniones de discovery en historias de usuario estructuradas con apoyo de IA. + +Reqs-AI SEO and Meta Tags + +Se propone utilizar un título claro y orientado al beneficio, una descripción breve con palabras clave relacionadas con el dominio y etiquetas Open Graph para mejorar la presentación cuando el enlace sea compartido en redes sociales o canales profesionales. También se recomienda definir una imagen de previsualización, metadatos de idioma, autor, viewport responsive y robots index/follow para las páginas públicas. + +Ejemplo de metadatos recomendados: + +```html +Reqs-AI | AI Requirements Elicitation Platform + + + + + + + + + +``` + +### 6.2.5. Navigation Systems + +El sistema de navegación de Reqs-AI se organiza mediante una navegación global persistente y una navegación contextual por módulo. La navegación global se presenta en la sidebar y permite acceder a las áreas principales de la aplicación: Dashboard, Projects, Sessions, User Stories, Integrations, Billing y Settings. Esta estructura se mantiene constante para que el usuario siempre tenga claridad sobre su ubicación. + +La navegación superior complementa la experiencia con el selector de workspace, búsqueda global, notificaciones, acciones rápidas y menú de perfil. El selector de workspace es clave porque Reqs-AI funciona bajo una lógica organizacional y permite operar sobre diferentes espacios de trabajo sin mezclar información entre clientes o equipos. + +Dentro de cada módulo se utilizan breadcrumbs, tabs, botones de acción y paneles laterales. Por ejemplo, en User Stories el usuario puede navegar del tablero general al detalle de una historia mediante un drawer lateral; en Integrations puede abrir el flujo de conexión con Jira mediante un modal; y en Settings puede alternar entre configuración del workspace, miembros, roles y billing. + +Este sistema de navegación busca mantener continuidad entre los flujos. El usuario puede pasar de una sesión de discovery a las historias generadas, luego revisar una historia, aprobarla y finalmente exportarla hacia Jira sin perder el contexto del proyecto ni del workspace activo. + +## 6.3. Landing Page UI Design + +El diseño de la interfaz de usuario (UI) para la Landing Page de Reqs-AI ha sido estructurado como el puente directo entre la propuesta de valor técnica de Kntro-Soft y las necesidades cognitivas de nuestros segmentos objetivos. Para lograrlo, traducimos los hallazgos del proceso de Need Finding y la arquitectura de información en una disposición visual que prioriza la claridad, la accesibilidad y el profesionalismo. Cada componente de la interfaz responde a decisiones estratégicas de diseño inclusivo: desde el uso de contrastes tipográficos que guían el flujo de lectura del usuario, hasta una jerarquía visual rigurosa que destaca de inmediato soluciones clave como la documentación instantánea en Gherkin y el asistente consultivo en tiempo real. Así, transformamos los requisitos funcionales de la plataforma en una experiencia digital intuitiva, limpia y optimizada para mitigar la fricción desde el primer punto de contacto. + +### 6.3.1. Landing Page Wireframe + +**Hero Section Wireframe** + +El Hero Section se posiciona en la parte superior de la página aplicando el principio de proximidad y foco visual, donde un titular de alto contraste comunica instantáneamente la propuesta de valor de Reqs-AI ("Documentación de requisitos asistida por IA en tiempo real"), acompañada de un subtítulo persuasivo y un botón de Call to Action (CTA) altamente accesible. Su disposición está optimizada arquitectónicamente mediante una estructura de una sola columna central en dispositivos móviles para facilitar el scrolling vertical, y un diseño de dos columnas en formato desktop que equilibra el texto con una captura intuitiva de la plataforma, guiando de forma fluida el recorrido visual del usuario sin generar sobrecarga cognitiva. + +![Reqs-AI Hero Section Wireframe](./assets/ui/landing/wireframes/hero-section-wireframe.png) + +**Benefits Section Wireframe** + +Esta sección presenta las funcionalidades clave mediante un diseño modular y limpio que respeta el principio de simplicidad, agrupando cada beneficio (documentación instantánea, asistencia en vivo y contexto inteligente) con un ícono descriptivo para agilizar la consistencia y el reconocimiento visual. En la versión web de escritorio se despliega una cuadrícula horizontal de tres columnas para una lectura comparativa rápida, mientras que en la versión móvil se adapta a una lista vertical de una sola columna que garantiza la adaptabilidad y una navegación táctil cómoda y fluida. + +![Reqs-AI Benefits Section Wireframe](./assets/ui/landing/wireframes/benefits-section-wireframe.png) + +**Features Section Wireframe** + +Esta sección expone el funcionamiento técnico de la plataforma mediante un flujo visual continuo que aplica el principio de secuencia lógica y continuidad. En la versión de escritorio, el proceso se divide en un diseño asimétrico de dos columnas alternadas que guían el recorrido del usuario paso a paso, mientras que la versión móvil lo consolida en una sola columna vertical con espaciados optimizados, asegurando una asimilación intuitiva y una alta legibilidad en pantallas táctiles. + +![Reqs-AI Features Section Wireframe](./assets/ui/landing/wireframes/features-section-wireframe.png) + +**Target Segments Section Wireframe** + +Esta sección expone los públicos objetivos aplicando el principio de contraste y segmentación clara de la información para diferenciar ambos perfiles. En la interfaz de escritorio se despliegan dos tarjetas simétricas en paralelo que permiten una comparativa directa e inmediata, mientras que en la versión móvil se apilan verticalmente para mantener una lectura limpia, un scroll cómodo y una correcta accesibilidad táctil. + +![Reqs-AI Target Segments Section Wireframe](./assets/ui/landing/wireframes/target-segments-section-wireframe.png) + +**Testimonials Section Wireframe** + +Esta sección refuerza la confianza de la plataforma aplicando el principio de prueba social mediante un diseño modular y limpio. En la versión de escritorio, los testimonios se organizan en una cuadrícula horizontal de tres columnas que permite una lectura rápida de las experiencias de los usuarios, mientras que en la versión móvil se adaptan a un carrusel o lista vertical de una sola columna que optimiza el espacio y facilita la navegación táctil. + +![Reqs-AI Testimonials Section Wireframe](./assets/ui/landing/wireframes/testimonials-section-wireframe.png) + +**Pricing Section Wireframe** + +Esta sección presenta los planes de suscripción de la plataforma aplicando el principio de contraste e incentivo visual para guiar la conversión de los distintos segmentos. En la versión de escritorio se despliega una estructura de tres columnas en paralelo destacando el plan "Professional" con una inversión de color (tarjeta negra), mientras que en la versión móvil las tarjetas se apilan verticalmente, manteniendo botones de acción (CTA) amplios y accesibles para una óptima interacción táctil. + +![Reqs-AI Pricing Section Wireframe](./assets/ui/landing/wireframes/pricing-section-wireframe.png) + +**Contact Section Wireframe** + +Esta sección facilita la conversión y el soporte directo aplicando los principios de claridad y accesibilidad en los canales de comunicación. En la interfaz de escritorio, los datos de contacto corporativo (correo electrónico y dirección física) se distribuyen de forma asimétrica junto a una descripción concisa para optimizar el espacio en pantalla, mientras que en la versión móvil los elementos se unifican en un flujo vertical de una sola columna que garantiza un acceso táctil inmediato y libre de fricciones. + +![Reqs-AI Contact Section Wireframe](./assets/ui/landing/wireframes/contact-section-wireframe.png) + +**Final CTA Section Wireframe** + +Esta sección actúa como el cierre de conversión de la página aplicando el principio de foco absoluto y aislamiento visual mediante un fondo oscuro de alto contraste. Su estructura simétrica de una sola columna centralizada, idéntica tanto en la versión de escritorio como en la móvil, elimina cualquier distracción secundaria para dirigir toda la atención del usuario hacia el botón principal de registro ("Get Started Now"), maximizando la accesibilidad y la intención de clic. + +![Reqs-AI Final CTA Section Wireframe](./assets/ui/landing/wireframes/final-cta-section-wireframe.png) + +**Footer Section Wireframe** + +Esta sección consolida el cierre de la página aplicando los principios de consistencia y arquitectura de información estructurada para la navegación secundaria. En la interfaz de escritorio se organiza mediante un diseño de cuadrícula asimétrica multicolumna que separa los derechos de autor y enlaces de redes sociales a la izquierda, distribuyendo de forma limpia las columnas temáticas de enlaces (Producto, Empresa, Soporte y Legal) a la derecha; mientras que en la versión móvil se unifica en una única columna de bloques apilados secuencialmente que optimiza el área táctil y la legibilidad en pantallas compactas. + +![Reqs-AI Footer Section Wireframe](./assets/ui/landing/wireframes/footer-section-wireframe.png) + +### 6.3.2. Landing Page Mock-up + +**Hero Section Mock-up** + +El Mock-up del Hero Section consolida la identidad visual del producto aplicando el Design System mediante una paleta de colores oscuros de alta fidelidad con acentos morados y una tipografía moderna Sans-Serif de alto contraste (blanco sobre fondo oscuro), garantizando la accesibilidad (WCAG). En la versión de escritorio se plasma la arquitectura de dos columnas donde el texto persuasivo coexiste armónicamente con una maqueta detallada de la interfaz de Reqs-AI, mientras que la versión móvil sintetiza este espacio en una disposición centralizada de una sola columna que optimiza la interacción táctil en los botones principales (Call to Action). + +![Reqs-AI Hero Section Mock-up](./assets/ui/landing/mockups/hero-section-mockup.png) + +**Benefits Section Mock-up** + +El Mock-up de la sección de beneficios consolida visualmente el núcleo de la problemática aplicando el Design System mediante un sofisticado fondo azul noche de baja luminosidad, el cual genera un contraste idóneo con los textos en blanco y los acentos en verde esmeralda para garantizar la conformidad con las pautas de accesibilidad. En la versión de escritorio, las tres tarjetas de problemáticas identificadas (pérdida de información, feedback infinito y costos exponenciales) se organizan en una cuadrícula simétrica de tres columnas equipadas con íconos vectoriales minimalistas que agilizan el reconocimiento visual, mientras que la interfaz móvil las unifica en una secuencia vertical de lectura directa que facilita la interacción táctil y preserva el balance del espacio negativo. + +![Reqs-AI Benefits Section Mock-up](./assets/ui/landing/mockups/benefits-section-mockup.png) + +**Features Section Mock-up** + +El Mock-up de esta sección implementa un fondo claro y limpio que destaca las cuatro funcionalidades principales mediante tarjetas individuales con bordes suavizados, respetando el principio de consistencia técnica del Design System. En la versión de escritorio, se aplica una distribución horizontal de cuatro columnas equipadas con contenedores de íconos en tonalidades pastel que agilizan la navegación visual, mientras que en la interfaz móvil este flujo se transforma en un desplazamiento vertical unificado para asegurar la accesibilidad tipográfica y una cómoda interacción táctil. + +![Reqs-AI Features Section Mock-up](./assets/ui/landing/mockups/features-section-mockup.png) + +**Target Segments Section Mock-up** + +El Mock-up de esta sección segmenta con precisión a los usuarios aplicando el Design System a través de dos grandes contenedores simétricos con sutiles bordes redondeados y tipografía oscura de alta legibilidad sobre un fondo gris claro neutro. En la versión de escritorio, las tarjetas de "Tech Leaders" y "Systems Analysts" se posicionan en paralelo para facilitar una lectura comparativa de sus dolores y beneficios específicos, integrando checkmarks de color verde esmeralda para una rápida asimilación visual; mientras que en la versión móvil se apilan de forma vertical para asegurar un escalado limpio y una óptima accesibilidad táctil. + +![Reqs-AI Target Segments Section Mock-up](./assets/ui/landing/mockups/target-segments-section-mockup.png) + +**Testimonials Section Mock-up** + +El Mock-up de esta sección materializa la prueba social aplicando el Design System mediante tarjetas individuales blancas que incorporan avatares circulares de alta definición y tipografía en cursiva para los testimonios corporativos. En la versión de escritorio, las opiniones se distribuyen horizontalmente en una cuadrícula simétrica de tres columnas que organiza la información de los líderes de la industria de forma limpia, mientras que en la versión móvil se adaptan a un ordenamiento vertical de una sola columna que garantiza la legibilidad tipográfica y una cómoda navegación táctil. + +![Reqs-AI Testimonials Section Mock-up](./assets/ui/landing/mockups/testimonials-section-mockup.png) + +**Pricing Section Mock-up** + +El Mock-up de la sección de precios plasma el modelo de monetización aplicando el Design System mediante tres tarjetas con tipografía limpia de gran escala para las tarifas (como el plan Starter de $49/mo enfocado en startups). En la versión de escritorio, se utiliza el principio de asimetría visual al destacar el plan "Professional" con un fondo azul oscuro profundo y un botón CTA verde esmeralda para atraer la conversión, mientras que en la versión móvil la interfaz se adapta a un apilamiento vertical que resguarda la proporción del espacio y garantiza la accesibilidad en la lectura de las características técnicas. + +![Reqs-AI Pricing Section Mock-up](./assets/ui/landing/mockups/pricing-section-mockup.png) + +**Contact Section Mock-up** + +El Mock-up de la sección de contacto materializa los canales de atención aplicando el Design System mediante iconografía lineal en color verde esmeralda y tipografía oscura de alta legibilidad sobre un fondo gris neutro. En la versión de escritorio, la información de soporte (hello@reqs.ai) y la dirección corporativa se despliegan con una alineación asimétrica a la izquierda que deja un respiro visual óptimo gracias al uso estratégico del espacio negativo, mientras que en la versión móvil todo el bloque se centraliza en una sola columna para garantizar una lectura directa y un acceso táctil inmediato. + +![Reqs-AI Contact Section Mock-up](./assets/ui/landing/mockups/contact-section-mockup.png) + +**Final CTA Section Mock-up** + +El Mock-up de la sección de cierre consolida la conversión aplicando el Design System mediante un bloque contenedor de color azul noche profundo y bordes suavizados que aísla visualmente el contenido para eliminar elementos distractores. Su estructura centralizada de una sola columna destaca un titular persuasivo de gran escala ("Stop wasting hours post-processing meeting recordings.") y un botón principal (CTA) verde esmeralda con alto contraste tipográfico, garantizando una interacción táctil intuitiva y una accesibilidad óptima tanto en la interfaz de escritorio como en la adaptación móvil. + +![Reqs-AI Final CTA Section Mock-up](./assets/ui/landing/mockups/final-cta-section-mockup.png) + +**Footer Section Mock-up** + +El Mock-up del Footer consolida la navegación secundaria aplicando el Design System mediante una tipografía Sans-Serif oscura de alta legibilidad sobre un fondo blanco limpio. En la versión de escritorio se implementa una cuadrícula asimétrica multicolumna que agrupa de manera lógica y ordenada las secciones del producto (Product, Company, Support y Legal) junto al logotipo, el lema corporativo y los íconos de redes sociales a la izquierda, mientras que en la versión móvil todos los bloques se apilan verticalmente de forma secuencial para maximizar las áreas de interacción táctil. + +![Reqs-AI Footer Section Mock-up](./assets/ui/landing/mockups/footer-section-mockup.png) + +## 6.4. Applications UX/UI Design + +En esta sección se presenta el diseño UX/UI de la aplicación web de Reqs-AI. El objetivo es mostrar cómo la solución organiza la experiencia del usuario dentro de la plataforma, desde el acceso inicial hasta la gestión de workspaces, proyectos, sesiones de descubrimiento, historias generadas por IA, integraciones, facturación y configuración del equipo. + +La propuesta de diseño se centra en validar la navegación, jerarquía visual, consistencia entre módulos y claridad de los flujos principales antes de pasar a la versión final de alta fidelidad. De esta manera, los wireframes permiten revisar la estructura funcional de la aplicación y los mock-ups permiten representar la apariencia final del producto. + +### 6.4.1. Applications Wireframes + +**Web Application Wireframes** + +Los wireframes de la aplicación web representan la versión de baja fidelidad de Reqs-AI. Su finalidad es validar la distribución de los elementos, la navegación interna, la secuencia de pantallas y los estados principales del sistema sin centrarse todavía en colores, estilos visuales o detalles gráficos finales. + +Estos wireframes cubren el recorrido principal del usuario: autenticación, creación del workspace, navegación por el dashboard, gestión de proyectos, configuración de sesiones, revisión de historias generadas por IA, integración con Jira, administración de facturación y configuración del equipo. + +**Autenticación y acceso** + +**Google Auth External Authorization Wireframe** + +**Descripción:** Representa la pantalla de autorización externa mediante Google. En baja fidelidad permite validar la ubicación del bloque de acceso federado, los permisos solicitados y el retorno seguro hacia Reqs-AI. + +Google Auth External Authorization Wireframe + +**Login Screen Wireframe** + +**Descripción:** Muestra la estructura base del inicio de sesión. Define la jerarquía de campos, acciones principales y acceso alternativo para que el usuario registrado ingrese a su workspace. + +Login Screen Wireframe + +**Signup Screen Wireframe** + +**Descripción:** Presenta la composición inicial del formulario de registro. Permite validar la disposición de campos, botones y enlaces necesarios para crear una cuenta en la plataforma. + +Signup Screen Wireframe + +**Onboarding y creación de workspace** + +**Dashboard Empty State Before Workspace Wireframe** + +**Descripción:** Expone el estado vacío del dashboard cuando el usuario aún no tiene un workspace. Este wireframe válida el mensaje guía y el llamado a la acción para iniciar el onboarding. + +Dashboard Empty State Before Workspace Wireframe + +**Workspace Creation Modal Empty Fields Wireframe** + +**Descripción:** Muestra el modal de creación de workspace sin datos ingresados. Sirve para validar la estructura del formulario y los campos mínimos requeridos para crear una organización. + +Workspace Creation Modal Empty Fields Wireframe + +**Workspace Creation Validation Errors Wireframe** + +**Descripción:** Representa los mensajes de validación del formulario. Permite comprobar cómo se informan errores cuando faltan datos obligatorios o cuando la información no cumple las reglas. + +Workspace Creation Validation Errors Wireframe + +**Workspace Company Type Dropdown Open Wireframe** + +**Descripción:** Muestra el selector de tipo de compañía abierto. Este estado permite validar cómo el usuario escoge el perfil de organización para contextualizar el uso inicial de Reqs-AI. + +Workspace Company Type Dropdown Open Wireframe + +**Workspace Team Size Dropdown Open Wireframe** + +**Descripción:** Presenta el selector de tamaño de equipo. Este wireframe ayuda a validar la captura de información necesaria para adaptar la experiencia del workspace. + +Workspace Team Size Dropdown Open Wireframe + +**Workspace Creation Form Filled Wireframe** + +**Descripción:** Muestra el formulario de workspace completado. Permite revisar cómo se presentan los datos antes de confirmar la creación del espacio de trabajo. + +Workspace Creation Form Filled Wireframe + +**Workspace Use Case Selection State Wireframe** + +**Descripción:** Representa la selección del caso de uso principal del workspace. Permite validar la organización visual de opciones vinculadas a discovery, requisitos, documentación e integración. + +Workspace Use Case Selection State Wireframe + +**Workspace Creation Loading State Wireframe** + +**Descripción:** Muestra el estado de carga durante la creación del workspace. Su objetivo es validar la retroalimentación visual mientras el sistema configura el entorno. + +Workspace Creation Loading State Wireframe + +**Workspace Created Success Modal Wireframe** + +**Descripción:** Presenta la confirmación de creación exitosa del workspace. Este wireframe válida el cierre del onboarding y la transición hacia el dashboard principal. + +Workspace Created Success Modal Wireframe + +**Dashboard y navegación principal** + +**Workspace Dashboard Home Wireframe** + +**Descripción:** Muestra la estructura general del dashboard con navegación lateral, barra superior, métricas y accesos rápidos. Permite validar la organización inicial de la aplicación interna. + +Workspace Dashboard Home Wireframe + +**Workspace Switcher Menu Open Wireframe** + +**Descripción:** Representa el selector de workspace abierto. Permite validar el cambio de contexto entre organizaciones o espacios de trabajo sin perder consistencia en la navegación. + +Workspace Switcher Menu Open Wireframe + +**User Profile Menu Open Wireframe** + +**Descripción:** Muestra el menú de perfil del usuario. Válida la ubicación de opciones personales, configuración de cuenta y cierre de sesión dentro de la interfaz global. + +User Profile Menu Open Wireframe + +**Gestión de proyectos** + +**Projects Page Overview Wireframe** + +**Descripción:** Presenta la vista general de proyectos. Permite validar la organización de listados, estados, acciones y acceso a la creación de nuevos proyectos dentro del workspace. + +Projects Page Overview Wireframe + +**Create New Project Modal Wireframe** + +**Descripción:** Muestra el modal para crear un proyecto. Permite validar la captura de información inicial del proyecto y la selección de plantilla o configuración base. + +Create New Project Modal Wireframe + +**Sesiones de descubrimiento** + +**Discovery Sessions Page Wireframe** + +**Descripción:** Representa la vista de sesiones de descubrimiento. Sirve para validar la tabla de sesiones, filtros, estados y acciones asociadas al procesamiento de reuniones. + +Discovery Sessions Page Wireframe + +**Live Session Configuration Modal Wireframe** + +**Descripción:** Muestra la configuración previa para iniciar una sesión en vivo. Permite validar campos, permisos y opciones necesarias para activar la captura asistida por IA. + +Live Session Configuration Modal Wireframe + +**Historias de usuario generadas por IA** + +**User Stories Review Board Wireframe** + +**Descripción:** Presenta el tablero de historias generadas por IA. Permite validar la disposición de tarjetas, métricas, estados de revisión y acciones de aprobación. + +User Stories Review Board Wireframe + +**User Story Detail Review Drawer Wireframe** + +**Descripción:** Muestra el detalle de una historia de usuario, incluyendo criterios de aceptación y revisión. Sirve para validar el panel lateral de edición, confianza y aprobación. + +User Story Detail Review Drawer Wireframe + +**Integraciones externas** + +**Integrations Page Jira Connection Wireframe** + +**Descripción:** Representa la página de integraciones externas. Válida la ubicación de la conexión con Jira, el estado de integración y las acciones de configuración. + +Integrations Page Jira Connection Wireframe + +**Jira Connection Modal Wireframe** + +**Descripción:** Muestra el asistente de conexión con Jira. Permite validar el flujo de autenticación, mapeo de proyectos y configuración de sincronización con Atlassian. + +Jira Connection Modal Wireframe + +**Billing y configuración** + +**Billing Subscription Page Wireframe** + +**Descripción:** Presenta la página de suscripción y facturación. Sirve para validar la visualización del plan activo, consumo, límites y opciones de actualización. + +Billing Subscription Page Wireframe + +**Settings Workspace Configuration Wireframe** + +**Descripción:** Muestra la configuración general del workspace. Permite validar la organización de secciones relacionadas con datos, preferencias, plan y controles administrativos. + +Settings Workspace Configuration Wireframe + +**Settings Team Members Management Wireframe** + +**Descripción:** Representa la subsección de miembros o perfil dentro de settings. Permite validar la gestión de usuarios, roles, invitaciones y control de acceso al workspace. + +Settings Team Members Management Wireframe + +**Navigation Consistency Check Frame Wireframe** + +**Descripción:** Evidencia la consistencia de navegación entre pantallas. Permite comprobar que sidebar, barra superior, perfil y acciones principales se mantienen uniformes. + +Navigation Consistency Check Frame Wireframe + +**Variantes complementarias del onboarding** + +**Workspace Creation Form Filled Alternative Wireframe** + +**Descripción:** Presenta una variante del formulario de workspace completado. Se usa para contrastar distribución de campos y confirmar que el flujo mantiene claridad antes de guardar. + +Workspace Creation Form Filled Alternative Wireframe + +**Workspace Settings Company Type Dropdown Wireframe** + +**Descripción:** Muestra una variante del selector de tipo de empresa dentro del flujo de configuración. Permite validar la selección del contexto organizacional. + +Workspace Settings Company Type Dropdown Wireframe + +**Workspace Use Case Selection Modal Wireframe** + +**Descripción:** Representa una versión alternativa del modal de selección de caso de uso. Ayuda a validar la lectura de opciones y la continuidad del onboarding. + +Workspace Use Case Selection Modal Wireframe + +**Workspace Initialization Loading Modal Wireframe** + +**Descripción:** Muestra una variante del estado de inicialización del workspace. Permite validar la comunicación de progreso mientras se preparan recursos internos. + +Workspace Initialization Loading Modal Wireframe + +**Workspace Created Success Details Wireframe** + +**Descripción:** Presenta una variante del mensaje de éxito con mayor detalle. Sirve para validar el cierre del flujo y la confirmación de elementos creados dentro del workspace. + +Workspace Created Success Details Wireframe + +**Mobile Application Wireframes** + +Los wireframes de la aplicación móvil representan la versión de baja fidelidad de Reqs-AI en formato adaptado para dispositivos móviles. Su objetivo es validar la estructura, navegación y flujos principales dentro de una interfaz táctil y de menor tamaño, manteniendo la consistencia funcional con la versión web. + +**Login Screen** + +**Descripción:** Esta pantalla gestiona el acceso seguro a la aplicación móvil aplicando el principio de simplicidad y reducción de la carga cognitiva mediante un diseño de una sola columna centralizada. El flujo prioriza la accesibilidad táctil ubicando los campos de entrada de texto (Email y Password) y el botón principal de acción (CTA) con dimensiones óptimas para evitar errores de pulsación, mientras que la arquitectura de información organiza los elementos de forma secuencial de arriba hacia abajo, garantizando una navegación intuitiva y directa desde el primer punto de contacto. + +Mobile Login Screen Wireframe + +**Dashboard Screen** + +**Descripción:** Esta pantalla actúa como el centro de control principal de la aplicación, estructurando la arquitectura de información mediante un flujo completamente vertical y modular para pantallas compactas. En la parte superior se integran tarjetas de métricas numéricas con barras de progreso que aplican el principio de consistencia visual para una asimilación inmediata del estado del proyecto, seguidas de una lista secuencial de elementos (Lorem Ipsum List) con contenedores amplios que mitigan el esfuerzo cognitivo. Para asegurar el diseño inclusivo y la accesibilidad táctil en movilidad, la pantalla incorpora un menú de navegación inferior (Bottom Navigation Bar) con etiquetas claras y un botón de acción flotante (FAB) estratégicamente posicionado en la esquina inferior derecha para facilitar el alcance con el pulgar. + +Mobile Dashboard Screen Wireframe + +**Settings & Profile Screen** + +**Descripción:** Esta vista gestiona la configuración de usuario agrupando la información en contenedores independientes que respetan el principio de proximidad y consistencia. La arquitectura de información distribuye verticalmente el perfil del usuario, el resumen de cuenta, las preferencias de apariencia, la seguridad, las notificaciones y las herramientas conectadas (como Jira o Trello); garantizando un diseño inclusivo mediante botones de acción grandes, etiquetas explícitas y un menú de navegación inferior consistente que facilita el control ergonómico con una sola mano. + +Mobile Settings & Profile Screen Wireframe + +**Live Assistant Screen** + +**Descripción:** Esta pantalla representa el núcleo interactivo en tiempo real de la solución móvil, estructurando un flujo de conversación dinámico optimizado para la escucha activa en juntas de trabajo. La arquitectura de información utiliza un diseño cronológico asimétrico de burbujas de diálogo que diferencia claramente las intervenciones de los participantes (Stakeholder) del procesamiento del sistema (Assistant), reduciendo drásticamente la sobrecarga cognitiva. + +Cumpliendo con los principios de diseño inclusivo y accesibilidad táctil en movilidad, la sección inferior integra un carrusel horizontal interactivo de sugerencias estratégicas inteligentes (AI Suggestions) con botones de acción rápida de gran tamaño (Use Suggestion), acompañados por una barra de herramientas de control ergonómico inferior de alta accesibilidad para operaciones inmediatas como silenciar, tomar notas o revisar artefactos generados. + +Mobile Live Assistant Screen Wireframe + +**Review & Export Screen** + +**Descripción:** Esta pantalla gestiona la revisión exhaustiva y exportación de artefactos mediante una arquitectura de información jerárquica y modular adaptada para entornos móviles. La interfaz implementa un sistema de pestañas superiores (Tabs) para alternar rápidamente entre categorías, seguido de tarjetas con listas de verificación individuales (Checkboxes) que permiten al usuario seleccionar historias de usuario específicas que despliegan su estructura sintáctica detallada. Para mitigar la fricción y asegurar un diseño inclusivo en movilidad, la pantalla superpone un panel emergente inferior (Bottom Sheet) de alta accesibilidad táctil que organiza las opciones de integración (como Jira, Azure, GitHub y Markdown) en una cuadrícula simétrica de botones amplios y fáciles de pulsar con una sola mano, agilizando el flujo de sincronización hacia repositorios externos de gestión. + +Mobile Review & Export Screen Wireframe + +**Projects Archive Screen** + +**Descripción:** Esta pantalla gestiona el catálogo de proyectos activos e históricos estructurando la arquitectura de información mediante un patrón jerárquico que facilita la búsqueda y el filtrado rápido en dispositivos móviles. En la parte superior se integra una barra de búsqueda (Search Input) seguida de píldoras de filtrado por estado (All, In Progress, Completed) que respetan el principio de consistencia técnica y proximidad, permitiendo refinar los resultados con un solo toque. El cuerpo principal organiza los proyectos en tarjetas modulares individuales que optimizan la legibilidad tipográfica; cada tarjeta agrupa de forma clara el estado del proyecto, el título, el cliente, la cantidad de historias de usuario y una barra de progreso porcentual. Finalmente, para garantizar el diseño inclusivo y la ergonomía táctil en movilidad, se incluye un botón de acción flotante (FAB) para añadir nuevos proyectos y una barra de navegación inferior (Bottom Navigation Bar) que asegura un control cómodo y libre de fricciones con una sola mano. + +Mobile Projects Archive Screen Wireframe + +**Project Settings Screen** + +**Descripción:** Esta vista gestiona los parámetros específicos de un proyecto individual organizando la arquitectura de información mediante una estructura puramente vertical que agrupa las configuraciones generales, el backend de sincronización, la gestión de miembros del equipo y los tokens de acceso. El diseño prioriza la accesibilidad y el control inclusivo en movilidad al implementar controles deslizantes (Switches) y menús desplegables con amplias áreas de activación táctil, optimizando la interacción con una sola mano y reduciendo la fatiga cognitiva del usuario. + +Mobile Project Settings Screen Wireframe + +**Session History Screen** + +**Descripción:** Esta pantalla organiza el historial cronológico de las sesiones de elicitación mediante una línea de tiempo vertical dividida por bloques temporales (Today, Yesterday) que reduce la carga cognitiva del usuario. La interfaz aplica los principios de jerarquía visual y diseño inclusivo al estructurar cada sesión en tarjetas independientes con identificadores únicos (#ID), etiquetas de estado y botones de acción de gran tamaño (View Transcript o Resume Session), permitiendo un acceso y control táctil inmediato en movilidad. + +Mobile Session History Screen Wireframe + +**Integrations Directory Screen** + +**Descripción:** Esta pantalla centraliza las herramientas externas conectadas organizando la arquitectura de información mediante categorías temáticas horizontales (Project Management, Communication) que simplifican el descubrimiento y la navegación en la interfaz móvil. El diseño fomenta el diseño inclusivo al implementar tarjetas independientes con botones de gran tamaño para conectar (Connect) o gestionar (Manage), complementado con un buscador superior accesible para mitigar el esfuerzo cognitivo y agilizar las tareas táctiles en movilidad. + +Mobile Integrations Directory Screen Wireframe + +**Billing & Subscription Screen** + +**Descripción:** Esta pantalla gestiona los detalles de facturación de la aplicación organizando la arquitectura de información de manera lineal y segregada mediante bloques de historial de transacciones, métodos de pago vinculados y tarjetas de suscripción activa. El diseño refuerza los principios de diseño inclusivo al incorporar botones de acción prominentes para la cancelación o actualización de planes, reduciendo la fricción en transacciones críticas y garantizando una interacción táctil transparente, ergonómica y libre de errores en movilidad. + +Mobile Billing & Subscription Screen Wireframe + +### 6.4.2. Applications Wireflow Diagrams + +**Web Application Wireflow Diagrams** + +Los wireflows de la aplicación web de Reqs-AI representan la conexión entre pantallas y estados interactivos del sistema. A diferencia de los wireframes, estos diagramas no solo muestran la estructura visual, sino también cómo el usuario avanza de una acción a otra dentro del flujo: autenticación, creación de workspace, navegación principal, gestión de proyectos, sesiones de descubrimiento, revisión de historias generadas por IA, integraciones, facturación y configuración del equipo. + +Cada wireflow incluye una flecha que indica la transición principal entre pantallas, permitiendo validar la continuidad de la experiencia, la coherencia de navegación y la relación entre módulos de la web application. + +**Flujo de autenticación y acceso** + +**Registro hacia inicio de sesión** + +**Descripción:** El flujo muestra cómo un visitante que se encuentra en la pantalla de registro puede volver al inicio de sesión mediante el enlace de acceso. Esta transición valida la navegación bidireccional entre autenticación y creación de cuenta. + +Registro hacia inicio de sesión + +**Inicio de sesión hacia autenticación con Google** + +**Descripción:** El flujo evidencia que el usuario puede seleccionar la opción de continuar con Google desde el login. Luego es redirigido al selector de cuenta externo, reduciendo fricción en el acceso. + +Inicio de sesión hacia autenticación con Google + +**Registro hacia autenticación con Google** + +**Descripción:** El flujo representa la alternativa de crear una cuenta usando Google desde la pantalla de registro. Permite validar que el onboarding también soporta autenticación federada. + +Registro hacia autenticación con Google + +**Inicio de sesión hacia confirmación de workspace creado** + +**Descripción:** El flujo muestra que, luego de autenticarse correctamente, el usuario puede ser llevado al estado de confirmación del workspace. Este paso cierra el acceso inicial y conecta con las acciones posteriores del entorno de trabajo. + +Inicio de sesión hacia confirmación de workspace creado + +**Flujo de onboarding y creación de workspace** + +**Confirmación de workspace hacia carga de configuración** + +**Descripción:** El flujo inicia en el modal de workspace creado y continúa hacia un estado de construcción del entorno. Válida que el sistema comunica el progreso mientras prepara módulos, contexto de IA y configuración inicial. + +Confirmación de workspace hacia carga de configuración + +**Workspace creado hacia configuración de tipo de empresa** + +**Descripción:** El flujo permite pasar desde el modal de éxito a la configuración del workspace. Se evidencia la selección del tipo de compañía para adaptar la experiencia al contexto de la organización. + +Workspace creado hacia configuración de tipo de empresa + +**Tipo de empresa hacia selección de caso de uso** + +**Descripción:** El flujo muestra cómo, después de seleccionar el tipo de organización, el usuario continúa hacia la definición del caso de uso principal. Esta transición ayuda a personalizar el workspace según el objetivo de trabajo. + +Tipo de empresa hacia selección de caso de uso + +**Selección de caso de uso hacia formulario de workspace** + +**Descripción:** El flujo representa el paso desde la selección de usos del workspace hacia el formulario de creación. Permite validar que el usuario puede completar la configuración general luego de definir su enfoque de trabajo. + +Selección de caso de uso hacia formulario de workspace + +**Formulario completo hacia validación de nuevo workspace** + +**Descripción:** El flujo muestra la acción de crear workspace desde un formulario completado y su transición a una variante de validación. Evidencia cómo la interfaz comunica errores o campos requeridos antes de guardar. + +Formulario completo hacia validación de nuevo workspace + +**Estado vacío hacia creación de workspace** + +**Descripción:** El flujo parte del dashboard sin workspace y lleva al modal de creación. Esta transición valida el onboarding principal cuando el usuario aún no tiene un espacio activo. + +Estado vacío hacia creación de workspace + +**Crear workspace hacia desplegable de tipo de compañía** + +**Descripción:** El flujo muestra la apertura del selector de tipo de compañía dentro del formulario. Permite comprobar que el usuario puede clasificar su organización antes de finalizar la creación. + +Crear workspace hacia desplegable de tipo de compañía + +**Tipo de compañía hacía caso de uso principal** + +**Descripción:** El flujo evidencia que, al completar la información organizacional, el usuario puede definir el propósito principal del workspace. Esta selección orienta el uso de Reqs-AI hacia requisitos, discovery o planificación. + +Tipo de compañía hacia caso de uso principal + +**Tamaño de equipo hacia selección de usos principales** + +**Descripción:** El flujo muestra la selección del tamaño del equipo y su avance hacia la pantalla de casos de uso. Permite validar que el onboarding recopila datos de escala y preferencias funcionales. + +Tamaño de equipo hacia selección de usos principales + +**Progreso de creación hacia workspace creado** + +**Descripción:** El flujo representa la transición desde el estado de creación en progreso hasta la confirmación exitosa. Válida que el usuario recibe retroalimentación clara cuando el espacio termina de configurarse. + +Progreso de creación hacia workspace creado + +**Workspace creado hacia página principal** + +**Descripción:** El flujo muestra cómo el usuario pasa desde el modal de éxito hacia el home del workspace. Esta transición permite iniciar la gestión de proyectos, sesiones e insights desde el dashboard. + +Workspace creado hacia página principal + +**Flujo de navegación principal e historias de usuario** + +**Home del workspace hacia historias de usuario** + +**Descripción:** El flujo evidencia la navegación desde el dashboard principal hacia el módulo de User Stories. Permite validar el acceso al backlog generado por IA desde el menú lateral. + +Home del workspace hacia historias de usuario + +**Listado de historias hacia panel de revisión** + +**Descripción:** El flujo muestra cómo una historia del listado se abre en un drawer de revisión. Allí se visualizan estado, prioridad, confianza de IA, descripción y criterios Gherkin para su aprobación. + +Listado de historias hacia panel de revisión + +**Selector de workspace hacia menú de perfil** + +**Descripción:** El flujo evidencia dos componentes globales de navegación: el selector de workspace y el menú de perfil. Válida que el usuario puede cambiar contexto y acceder a opciones personales desde la cabecera. + +Selector de workspace hacia menú de perfil + +**Flujo de proyectos y sesiones de descubrimiento** + +**Selector de workspace hacía proyectos** + +**Descripción:** El flujo muestra la transición desde la navegación global hacia la página de proyectos. Permite comprobar que el usuario puede ubicarse en un workspace y administrar sus iniciativas. + +Selector de workspace hacia proyectos + +**Proyectos hacia creación de nuevo proyecto** + +**Descripción:** El flujo parte del listado de proyectos y abre el modal de creación. Permite validar la captura del nombre, descripción, plantilla y visibilidad del proyecto. + +Proyectos hacia creación de nuevo proyecto + +**Menú de perfil hacia sesiones** + +**Descripción:** El flujo muestra la navegación hacia el módulo de Sessions desde la estructura interna de la aplicación. Permite validar la consulta de reuniones procesadas, métricas y acciones disponibles. + +Menú de perfil hacia sesiones + +**Sesiones hacia vista de discovery sessions** + +**Descripción:** El flujo representa el cambio desde la vista completa de sesiones hacia una vista resumida de discovery sessions. Permite revisar métricas, sesiones recientes y estado de procesamiento. + +Sesiones hacia vista de discovery sessions + +**Discovery sessions hacia inicio de sesión en vivo** + +**Descripción:** El flujo muestra cómo el usuario abre el modal para iniciar una sesión de descubrimiento en vivo. Se configuran título, proyecto asociado, modo de facilitación y opciones de captura antes de lanzar la sesión. + +Discovery sessions hacia inicio de sesión en vivo + +**Sesiones hacia integraciones** + +**Descripción:** El flujo evidencia la navegación desde sesiones hacia el módulo de integraciones. Permite conectar los resultados del levantamiento con herramientas externas como Jira, Confluence, GitHub, Slack o Miro. + +Sesiones hacia integraciones + +**Flujo de integraciones, billing y configuración** + +**Integraciones hacia conexión con Jira** + +**Descripción:** El flujo muestra cómo el usuario inicia la configuración de Jira desde la página de integraciones. El modal guía la autenticación, la conexión del sitio Atlassian y la preparación del mapeo. + +Integraciones hacia conexión con Jira + +**Billing hacia configuración del workspace** + +**Descripción:** El flujo conecta la página de facturación con settings. Permite validar que el usuario puede revisar su plan, consumo y método de pago, y luego administrar configuración organizacional. + +Billing hacia configuración del workspace + +**Settings hacia gestión de equipo** + +**Descripción:** El flujo muestra la navegación desde la configuración general hacia Team Management. Permite administrar miembros, invitaciones, roles, permisos y alertas del equipo. + +Settings hacia gestión de equipo + +**Gestión de equipo hacia home con notificaciones** + +**Descripción:** El flujo evidencia el retorno desde Team Management hacia el dashboard principal, donde se despliega el panel de notificaciones. Permite validar la continuidad de navegación y comunicación de eventos del sistema. + +Gestión de equipo hacia home con notificaciones + +**Mobile Application Wireflow Diagrams** + +**Autenticación de usuario y gestión de perfil** + +**Descripción:** Este Wireflow detalla el flujo secuencial orientado al objetivo del usuario de iniciar sesión de forma segura y navegar de inmediato hacia la gestión de su cuenta. El diagrama mapea la transición interactiva desde la pantalla de Login centralizada hasta la vista de Settings & Profile, evidenciando la persistencia de la arquitectura de información y la consistencia del menú de navegación inferior durante el desplazamiento ergonómico en la aplicación móvil. + +Mobile Login to Profile Management Wireflow + +**Navegación del Dashboard a Live Assistant** + +**Descripción:** Este Wireflow ilustra el flujo interactivo diseñado para cumplir con el objetivo del usuario de iniciar o acceder rápidamente a una sesión de elicitación de requerimientos en tiempo real. El diagrama define la transición directa desde el Dashboard principal mediante la barra de navegación inferior hacia la pantalla del Live Assistant, asegurando que el analista pueda monitorear la transcripción y recibir sugerencias inteligentes de la IA de manera inmediata y sin fricciones de navegación. + +Mobile Dashboard to Live Assistant Wireflow + +**Navegación del Dashboard a Review & Export** + +**Descripción:** Este Wireflow traza el camino interactivo enfocado en el objetivo del usuario de examinar, validar y exportar los requerimientos generados hacia repositorios externos. El diagrama mapea la navegación desde la vista principal del Dashboard hacia la pantalla de Review & Export, ilustrando cómo la interfaz despliega de forma adaptativa el menú emergente inferior (Bottom Sheet) para facilitar la sincronización táctil con herramientas de gestión de proyectos sin perder el contexto de la aplicación móvil. + +Mobile Dashboard to Review & Export Wireflow + +**Gestión Integral de Proyectos y Requerimientos** + +**Descripción:** Este Wireflow mapea el flujo interactivo de extremo a extremo diseñado para que el analista administre la configuración y los artefactos de un proyecto. El diagrama detalla la ruta secuencial que inicia en el Dashboard, transiciona por el catálogo de Projects Archive mediante la barra de navegación, ingresa a las configuraciones detalladas en Project Settings y culmina en la vista de Project User Stories, demostrando cómo se mantiene la jerarquía visual y la consistencia en el control de estados críticos a través de la aplicación móvil. + +Mobile Project Management Flow Wireflow + +**Navegación de Historias de Usuario a Historial, Integraciones y Facturación** + +**Descripción:** Este Wireflow traza el flujo transversal que permite al analista navegar fluidamente entre el control operativo y administrativo del sistema. El diagrama detalla la transición secuencial iniciada en la vista de Project User Stories, pasando secuencialmente a través del menú de navegación inferior hacia Session History para auditar transcripciones previas, luego hacia el Integrations Directory para conectar servicios de comunicación externos, y culminando directamente en Billing & Subscription para la gestión financiera de la cuenta móvil. + +Mobile User Stories to History, Integrations, Billing Wireflow + +### 6.4.2. Applications Mock-ups + +**Web Application Mock-ups** + +Los siguientes mock-ups presentan la versión de alta fidelidad de la aplicación web de Reqs-AI. La secuencia evidencia el recorrido principal del usuario dentro de la plataforma: autenticación, creación del workspace, navegación inicial, gestión de proyectos, sesiones de descubrimiento, revisión de historias generadas por IA, integraciones, facturación y configuración del equipo. + +**Autenticación y acceso** + +**Google Auth External Authorization** + +**Descripción:** Esta pantalla representa el flujo de autorización externa mediante Google. Permite evidenciar que Reqs-AI contempla un acceso rápido y seguro usando una cuenta existente, reduciendo la fricción del registro manual. + +Google Auth External Authorization + +**Login Screen** + +**Descripción:** Este mock-up muestra la pantalla de inicio de sesión de Reqs-AI. Aquí el usuario ingresa sus credenciales para acceder a sus proyectos, sesiones e historial dentro de la organización activa. + +Login Screen + +**Signup Screen** + +**Descripción:** Esta pantalla representa el registro de una nueva cuenta. El formulario permite que un visitante se convierta en usuario de la plataforma para posteriormente crear o asociarse a un workspace. + +Signup Screen + +**Onboarding y creación de workspace** + +**Dashboard Empty State Before Workspace** + +**Descripción:** Este mock-up muestra el estado inicial del dashboard cuando el usuario aún no ha creado ningún workspace. Funciona como punto de onboarding y guía al usuario hacia la creación de su primer espacio de trabajo. + +Dashboard Empty State Before Workspace + +**Workspace Creation Modal Empty Fields** + +**Descripción:** Esta pantalla presenta la modal inicial de creación de workspace con los campos vacíos. Su propósito es capturar los datos básicos de la organización o equipo que usará Reqs-AI. + +Workspace Creation Modal Empty Fields + +**Workspace Creation Validation Errors** + +**Descripción:** Este mock-up evidencia las validaciones del formulario de creación de workspace. La interfaz informa al usuario cuando faltan campos obligatorios o cuando la información ingresada no cumple las reglas esperadas. + +Workspace Creation Validation Errors + +**Workspace Creation Company Type Dropdown Open** + +**Descripción:** Esta pantalla muestra el selector desplegable para elegir el tipo de empresa u organización. Este dato ayuda a contextualizar el uso de Reqs-AI según el perfil del equipo. + +Workspace Creation Company Type Dropdown Open + +**Workspace Creation Form Filled Private Visibility** + +**Descripción:** Este mock-up representa el formulario de workspace completado, incluyendo la configuración de visibilidad privada. Permite revisar la información antes de confirmar la creación del entorno. + +Workspace Creation Form Filled Private Visibility + +**Workspace Creation Team Size Dropdown Open** + +**Descripción:** Esta pantalla muestra el selector de tamaño del equipo. La selección permite adaptar la experiencia inicial y las recomendaciones del sistema según la cantidad de miembros del workspace. + +Workspace Creation Team Size Dropdown Open + +**Workspace Settings Company Type Dropdown Open** + +**Descripción:** Este mock-up representa un estado de configuración donde se puede revisar o ajustar el tipo de organización. Refuerza que los datos del workspace pueden actualizarse según la realidad del equipo. + +Workspace Settings Company Type Dropdown Open + +**Workspace Onboarding Use Case Selection State** + +**Descripción:** Esta pantalla muestra la selección del caso de uso principal del workspace. Permite orientar la plataforma hacia discovery, levantamiento de requisitos, generación de historias o integración con herramientas ágiles. + +Workspace Onboarding Use Case Selection State + +**Workspace Use Case Selection Modal** + +**Descripción:** Este mock-up presenta el modal donde el usuario define el enfoque inicial de uso de Reqs-AI. Esta decisión ayuda a personalizar el onboarding y las siguientes acciones dentro de la plataforma. + +Workspace Use Case Selection Modal + +**Workspace Setup Summary Form Filled** + +**Descripción:** Esta pantalla funciona como resumen previo a la creación definitiva del workspace. El usuario puede validar los datos ingresados antes de confirmar el espacio de trabajo. + +Workspace Setup Summary Form Filled + +**Workspace Creation Loading State** + +**Descripción:** Este mock-up muestra el estado de carga después de confirmar la creación del workspace. Comunica que el sistema está procesando la solicitud y evita acciones repetidas. + +Workspace Creation Loading State + +**Workspace Creation Progress Loading State** + +**Descripción:** Esta pantalla representa un estado de progreso durante la creación del workspace. Muestra que el sistema está configurando el entorno, preparando datos iniciales y habilitando el acceso. + +Workspace Creation Progress Loading State + +**Workspace Created Success Modal** + +**Descripción:** Este mock-up evidencia la confirmación de creación exitosa del workspace. La interfaz informa que el espacio ya está listo y permite continuar hacia el dashboard principal. + +Workspace Created Success Modal + +**Workspace Created Success Details Modal** + +**Descripción:** Esta pantalla complementa el mensaje de éxito con detalles del workspace creado. Refuerza el cierre del flujo de onboarding y da claridad sobre el nuevo entorno de trabajo. + +Workspace Created Success Details Modal + +**Dashboard y navegación principal** + +**Workspace Dashboard Home** + +**Descripción:** Este mock-up muestra el dashboard principal luego de crear el workspace. Presenta una vista general de actividad, accesos rápidos y métricas iniciales. + +Workspace Dashboard Home + +**Workspace Switcher Menu Open** + +**Descripción:** Esta pantalla evidencia el selector de workspace abierto. Permite cambiar entre organizaciones o espacios de trabajo, asegurando que el usuario opere en el contexto correcto. + +Workspace Switcher Menu Open + +**User Profile Menu Open** + +**Descripción:** Este mock-up muestra el menú de perfil del usuario. Desde esta sección se accede a opciones personales, configuración de cuenta o cierre de sesión. + +User Profile Menu Open + +**Gestión de proyectos** + +**Projects Page Overview** + +**Descripción:** Este mock-up presenta la vista principal de proyectos dentro del workspace. Permite visualizar proyectos activos, su estado y accesos para crear o administrar iniciativas. + +Projects Page Overview + +**Projects Board Overview** + +**Descripción:** Esta pantalla muestra una vista tipo tablero de proyectos. Facilita comparar proyectos, revisar su avance y acceder a sesiones o historias relacionadas. + +Projects Board Overview + +**Live Discovery Session Modal Configuration** + +**Descripción:** Esta pantalla representa la configuración de una sesión de descubrimiento en vivo. El usuario puede preparar la captura de audio y activar el soporte de IA para convertir la conversación en historias de usuario. + +Live Discovery Session Modal Configuration + +**Historias de usuario generadas por IA** + +**User Stories Page Review Board** + +**Descripción:** Este mock-up muestra el tablero de revisión de historias de usuario generadas por IA. Las historias pueden organizarse por estado, revisarse, editarse y aprobarse antes de pasar al backlog. + +User Stories Page Review Board + +**Integraciones, billing y configuración** + +**Integrations Page Jira Connection** + +**Descripción:** Este mock-up muestra la página de integraciones externas, destacando la conexión con Jira. Permite evidenciar cómo Reqs-AI facilita llevar historias aprobadas hacia herramientas ágiles. + +Integrations Page Jira Connection + +**Jira Connection Modal OAuth Flow** + +**Descripción:** Esta pantalla representa el modal de conexión con Jira mediante autorización OAuth. El objetivo es vincular Reqs-AI con Atlassian de forma segura, sin exponer credenciales directamente. + +Jira Connection Modal OAuth Flow + +**Billing Subscription Page** + +**Descripción:** Este mock-up presenta la página de suscripción y facturación. Permite visualizar el plan activo, consumo, límites y opciones de actualización del modelo SaaS. + +Billing Subscription Page + +**Settings Workspace Configuration Page** + +**Descripción:** Esta pantalla muestra la configuración general del workspace. Desde aquí se gestionan datos de la organización, preferencias del entorno y ajustes principales. + +Settings Workspace Configuration Page + +**Navigation Consistency Check Frame** + +**Descripción:** Esta pantalla evidencia la consistencia visual de la navegación en la aplicación. Mantiene sidebar, barra superior, acciones principales y perfil de usuario de forma uniforme. + +Navigation Consistency Check Frame + +**Settings Team Members Management Page** + +**Descripción:** Este mock-up representa la administración de miembros del equipo. Permite invitar usuarios, revisar integrantes, gestionar roles y controlar accesos al workspace. + +Settings Team Members Management Page + +**Sesiones de descubrimiento** + +**Discovery Sessions Page Simple Overview** + +**Descripción:** Esta pantalla muestra una vista general de las sesiones de descubrimiento. Permite revisar reuniones registradas, su estado y el acceso a sesiones anteriores. + +Discovery Sessions Page Simple Overview + +**Discovery Sessions Page Metrics And Export** + +**Descripción:** Este mock-up amplía la vista de sesiones con métricas y acciones de exportación. Permite evidenciar el valor generado por Reqs-AI mediante sesiones procesadas y resultados obtenidos. + +Discovery Sessions Page Metrics And Export + +**User Story Review Drawer With Gherkin** + +**Descripción:** Esta pantalla presenta el panel lateral de detalle de una historia de usuario. Incluye descripción, criterios de aceptación en formato Gherkin, nivel de confianza y acciones de edición o aprobación. + +User Story Review Drawer With Gherkin + +**Create New Project Modal Template Selection** + +**Descripción:** Este mock-up representa el modal de creación de un nuevo proyecto. Incluye la selección de plantilla o tipo de proyecto para configurar rápidamente un espacio de levantamiento de requisitos. + +Create New Project Modal Template Selection + +**Mobile Application Mock-ups** + +Los siguientes mock-ups presentan la versión de alta fidelidad de la aplicación móvil de Reqs-AI. La secuencia evidencia el recorrido principal del usuario dentro de la plataforma: autenticación, navegación principal, gestión de proyectos, sesiones de descubrimiento, revisión de historias generadas por IA, integraciones, facturación y configuración del equipo. + +**Login Screen** + +**Descripción:** El Mock-up de la pantalla de inicio de sesión consolida la identidad de marca del producto aplicando el Design System mediante una paleta de colores sobria, donde destaca el verde esmeralda corporativo en el botón principal de acción (CTA) y los acentos interactivos (Sign In, Forgot Password?). La arquitectura de información distribuye verticalmente los elementos clave (campos de texto legibles, opciones de autenticación federada con Google y Okta, y enlaces de asistencia) sobre una tarjeta contenedora blanca con bordes redondeados y sombras sutiles que generan una clara separación de capas visuales, garantizando un alto contraste tipográfico y un diseño inclusivo que minimiza el error táctil en dispositivos móviles. + +Mobile Login Screen Mockup + +**Dashboard Screen** + +**Descripción:** El Mock-up del Dashboard implementa el Design System estructurando métricas clave (como los 12 proyectos activos o las 450 historias generadas) en tarjetas modulares blancas de alta visibilidad que optimizan la carga cognitiva. Su arquitectura de información destaca una sección interactiva de "Recent Sessions" y un contenedor asimétrico azul noche para recomendaciones de la IA, complementado con un menú de navegación inferior consistente y un botón flotante de micrófono esmeralda que garantiza una accesibilidad táctil inmediata en movilidad. + +Mobile Dashboard Screen Mockup + +**Settings & Profile Screen** + +**Descripción:** El Mock-up de esta vista consolida las configuraciones de la cuenta organizando la arquitectura de información mediante contenedores modulares con esquinas suavizadas sobre un fondo gris claro neutro. La interfaz integra con precisión los elementos de diseño del Design System, destacando el uso de etiquetas de estado en verde esmeralda para suscripciones activas (Enterprise), selectores de apariencia con alto contraste y un botón de cierre de sesión (Log Out) codificado en rojo para mitigar errores de navegación táctil. + +Mobile Settings & Profile Screen Mockup + +**Live Assistant Screen** + +**Descripción:** El Mock-up de esta pantalla operativa plasma la interacción de la IA en tiempo real utilizando el Design System mediante un contenedor blanco de bordes suavizados y un sutil degradado verde esmeralda para el módulo "AI Smart Suggestions". La arquitectura de información prioriza la accesibilidad y ergonomía táctil en movilidad al ubicar los controles de sesión en la parte inferior, destacando el botón principal asimétrico (Finish & Generate Stories) para cerrar el flujo libre de errores. + +Mobile Live Assistant Screen Mockup + +**Review & Export Screen** + +**Descripción:** El Mock-up de esta pantalla aplica el Design System superponiendo un panel emergente inferior (Bottom Sheet) estilizado con sombras suaves para confirmar la sincronización hacia plataformas de gestión de proyectos. La arquitectura de información destaca una cuadrícula de botones para seleccionar servicios externos (como Azure DevOps en verde corporativo) y un botón de acción principal de alto contraste (Sync Backlog), garantizando un diseño inclusivo y un control táctil óptimo sin perder la visibilidad de las historias de usuario de fondo. + +Mobile Review & Export Screen Mockup + +**Projects Archive Screen** + +**Descripción:** El Mock-up de esta pantalla consolida el catálogo de proyectos empleando tarjetas modulares individuales con bordes suavizados que facilitan la lectura en movilidad. Su arquitectura de información integra un buscador superior intuitivo y píldoras de filtrado por estado, aplicando las pautas de diseño inclusivo del Design System mediante barras de progreso en verde esmeralda y un botón flotante de adición táctil de alta accesibilidad para el pulgar. + +Mobile Projects Archive Screen Mockup + +**Project Settings Screen** + +**Descripción:** El Mock-up de esta pantalla configura los parámetros del proyecto aplicando el Design System mediante un diseño puramente vertical segmentado en bloques funcionales con tipografía de alto contraste. La arquitectura de información destaca controles interactivos táctiles como el switch esmeralda de "AI Auto-Analysis Settings", tarjetas de integración directa (Jira y Azure) y un componente cronológico de "Team Activity", garantizando un diseño inclusivo y un control ergonómico sin errores en movilidad. + +Mobile Project Settings Screen Mockup + +**Project User Stories Screen** + +**Descripción:** El Mock-up de esta pantalla presenta el catálogo detallado de historias de usuario bajo una arquitectura de información estructurada en bloques de sintaxis Gherkin (Given-When-Then) de alta legibilidad para el analista. La interfaz consolida el Design System mediante un buscador superior interactivo, píldoras de filtrado por estado (Draft, Review, Approved) codificadas por colores de alto contraste y un botón flotante de adición táctil que optimiza el diseño inclusivo en movilidad. + +Mobile Project User Stories Screen Mockup + +**Session History Screen** + +**Descripción:** El Mock-up de esta pantalla organiza las grabaciones pasadas aplicando el Design System sobre una línea de tiempo vertical segmentada de manera limpia con indicadores de estado circulares. La arquitectura de información destaca en cada tarjeta métricas críticas como el porcentaje de precisión de la IA (AI Score), la duración y el moderador, integrando un diseño inclusivo mediante botones de reproducción rápida en verde esmeralda y accesos prominentes (View Transcript) para mitigar errores en movilidad. + +Mobile Session History Screen Mockup + +**Integrations Directory Screen** + +**Descripción:** El Mock-up de esta pantalla consolida el catálogo de extensiones externas aplicando el Design System mediante tarjetas modulares ordenadas bajo íconos de categoría con alto contraste cromático. La arquitectura de información distribuye de forma jerárquica los servicios vinculados (como Jira en estado activo) de los disponibles para conectar (Connect), integrando un diseño inclusivo mediante botones amplios de fácil alcance y un banner asimétrico azul noche para promocionar integraciones destacadas de la IA. + +Mobile Integrations Directory Screen Mockup + +**Billing & Subscription Screen** + +**Descripción:** El Mock-up de esta pantalla gestiona los datos financieros del usuario aplicando el Design System a través de bloques de información con bordes suavizados sobre un fondo blanco limpio. La arquitectura de información prioriza los datos críticos mediante una barra de progreso esmeralda para el consumo de tokens, una tarjeta asimétrica azul noche para el próximo cobro de $499.00 y una lista vertical de facturas descargables con botones táctiles amplios, logrando un diseño inclusivo que simplifica la administración de la cuenta en movilidad. + +Mobile Billing & Subscription Screen Mockup + +**Web Application User Flow Diagrams** + +Los user flow diagrams de la aplicación web de Reqs-AI representan el recorrido funcional que sigue el usuario dentro de la plataforma. A diferencia de los wireframes y wireflows, estos diagramas permiten observar de forma más directa cómo se conectan las acciones principales del usuario con los módulos del sistema: autenticación, onboarding, creación de workspace, navegación principal, gestión de proyectos, sesiones de descubrimiento, revisión de historias generadas por IA, integraciones, facturación y configuración del equipo. + +Cada user flow evidencia una ruta de uso concreta, mostrando cómo el usuario avanza desde una necesidad inicial hasta una acción final dentro de la web application. Esto permite validar la continuidad de la experiencia, la coherencia entre módulos y la relación entre las pantallas diseñadas. + +#### Flujo de autenticación y acceso + +##### Registro hacia inicio de sesión + +**Descripción:** Este user flow muestra la transición entre la pantalla de registro y la pantalla de inicio de sesión. El flujo permite validar que un visitante puede crear una cuenta o volver al login si ya cuenta con credenciales, manteniendo una navegación clara en el acceso inicial a Reqs-AI. + +Registro hacia inicio de sesión + +#### Flujo de onboarding y creación de workspace + +##### Dashboard vacío hacia configuración del workspace + +**Descripción:** Este user flow representa el recorrido inicial del usuario cuando aún no tiene un workspace creado. Desde el dashboard vacío, el usuario puede iniciar la creación del espacio de trabajo, seleccionar el caso de uso principal y completar la configuración base para adaptar Reqs-AI a su organización. + +Dashboard vacío hacia configuración del workspace + +##### Creación del workspace hacia confirmación exitosa + +**Descripción:** Este flujo evidencia el proceso de carga y creación del workspace. La pantalla muestra cómo el sistema comunica el avance de configuración y luego confirma que el workspace fue creado correctamente, permitiendo al usuario continuar hacia el entorno principal de trabajo. + +Creación del workspace hacia confirmación exitosa + +#### Flujo de navegación principal y proyectos + +##### Home del workspace hacia proyectos + +**Descripción:** Este user flow muestra la navegación desde el dashboard principal del workspace hacia el módulo de proyectos. Permite validar que el usuario puede revisar proyectos activos, acceder a métricas generales y administrar iniciativas asociadas a clientes o productos específicos. + +Home del workspace hacia proyectos + +#### Flujo de sesiones de descubrimiento + +##### Sesiones hacia discovery sessions + +**Descripción:** Este flujo representa la navegación desde la página general de sesiones hacia la vista de discovery sessions. Permite revisar reuniones procesadas, métricas de sesiones, estados de avance y accesos a sesiones asistidas por IA. + +Sesiones hacia discovery sessions + +##### Discovery sessions hacia inicio de sesión en vivo + +**Descripción:** Este user flow muestra cómo el usuario inicia una sesión de descubrimiento en vivo desde el módulo de sesiones. El flujo valida la configuración previa de la sesión, incluyendo título, proyecto asociado, modo de facilitación y opciones de captura antes de lanzar la reunión asistida por IA. + +Discovery sessions hacia inicio de sesión en vivo + +#### Flujo de historias de usuario generadas por IA + +##### Historias de usuario hacia revisión detallada + +**Descripción:** Este flujo evidencia cómo el usuario accede al detalle de una historia generada por IA desde el listado principal. El panel de revisión permite validar la descripción, prioridad, nivel de confianza, criterios de aceptación en formato Gherkin y acciones de edición o aprobación antes de utilizar la historia en el backlog. + +Historias de usuario hacia revisión detallada + +#### Flujo de integraciones externas + +##### Integraciones hacia conexión con Jira + +**Descripción:** Este user flow muestra el proceso para conectar Reqs-AI con Jira desde la página de integraciones. El flujo permite validar la autorización, configuración y vinculación con Atlassian para exportar historias aprobadas hacia herramientas ágiles sin copiar información manualmente. + +Integraciones hacia conexión con Jira + +#### Flujo de facturación y suscripción + +##### Gestión de billing y suscripción + +**Descripción:** Este flujo representa la vista de facturación y suscripción del workspace. Permite revisar el plan activo, el consumo de tokens de IA, los colaboradores activos, el método de pago, el historial de facturas y las acciones de upgrade o administración del plan. + +Gestión de billing y suscripción + +#### Flujo de configuración y equipo + +##### Settings hacia gestión de equipo + +**Descripción:** Este user flow muestra la navegación desde la configuración general del workspace hacia la administración del equipo. Permite validar la gestión de miembros, roles, permisos, invitaciones y alertas internas, asegurando control sobre el acceso y las responsabilidades dentro de la organización. + +Settings hacia gestión de equipo + +#### Flujo general de la web application + +##### Vista general del user flow completo + +**Descripción:** Este diagrama resume el recorrido global de la web application de Reqs-AI. Integra las rutas principales entre dashboard, proyectos, sesiones, historias de usuario, integraciones, billing y settings, mostrando cómo los módulos se conectan dentro de una experiencia continua y coherente para el usuario. + +Vista general del user flow completo + +**Mobile Application User Flow Diagrams** + +**Autenticación de Usuario y Configuración de Cuenta** + +**Descripción:** Este User Flow mapea el happy path de alta fidelidad para el inicio de sesión exitoso y la navegación inmediata hacia la configuración del perfil mediante el menú inferior. El diagrama integra los Mock-ups finales del producto digital para validar las condiciones visuales del Design System, sirviendo como contraparte directa y consistente del Wireflow funcional previamente establecido. + +Mobile Authentication and Profile User Flow + +**Activación de Sesión de Elicitación en Live Assistant** + +**Descripción:** Este User Flow ilustra la ruta óptima (happy path) de alta fidelidad que recorre el analista al iniciar una sesión de captura de requerimientos desde el Dashboard principal. El diagrama emplea los Mock-ups terminados para validar visualmente la activación inmediata del motor de IA (Analysis engine active) y el despliegue dinámico de sugerencias en tiempo real al cambiar de pantalla. + +Mobile Live Assistant User Flow + +**Sincronización y Exportación de Requerimientos Refinados** + +**Descripción:** Este User Flow representa la ruta esperada (happy path) de alta fidelidad para la validación y exportación de artefactos de software directamente a herramientas de gestión desde el Dashboard. El diagrama incorpora los Mock-ups finales para ilustrar la interacción táctil que despliega el panel inferior de sincronización (Ready for Backlog Sync), garantizando la consistencia visual y de comportamiento con su respectivo diagrama de Wireflow. + +Mobile Review & Export User Flow + +**Administración de Proyectos y Gestión de Historias de Usuario** + +**Descripción:** Este User Flow detalla la ruta interactiva (happy path) de alta fidelidad que recorre el usuario para supervisar la configuración y el backlog de un proyecto específico. El diagrama conecta en secuencia los Mock-ups finales del producto digital (Dashboard, Projects Archive, Project Settings y Project User Stories), validando de forma consistente las transiciones visuales de los datos y estados definidos previamente en la estructura funcional del Wireflow. + +Mobile Project Management User Flow + +**Navegación de Historias de Usuario a Historial, Integraciones y Facturación** + +**Descripción:** Este último User Flow consolida el camino operativo (happy path) de alta fidelidad que recorre el analista para transicionar entre la gestión del backlog y los módulos administrativos. El diagrama enlaza secuencialmente los Mock-ups finales (Project User Stories, Session History, Integrations Directory y Billing & Subscription) mediante interacciones en el menú inferior, garantizando una correspondencia estética y funcional exacta con el comportamiento definido en su Wireflow homólogo. + +Mobile User Stories to History, Integrations, Billing User Flow + +## 6.5. Applications Prototyping + +**Web Application Prototyping** + +En esta sección se presenta el prototipo interactivo de alta fidelidad para la aplicación web de Reqs-AI, construido con Figma. El prototipo integra los mock-ups finales en una experiencia navegable que simula el comportamiento real de la plataforma, permitiendo validar la usabilidad, la consistencia visual y la fluidez de las transiciones entre las pantallas principales. + +El prototipo web permite recorrer los flujos más importantes del sistema, como el inicio de sesión, la creación del workspace, la navegación por el dashboard, la gestión de proyectos, el inicio de sesiones de descubrimiento, la revisión de historias de usuario generadas por IA, la conexión con Jira, la administración de la suscripción y la configuración del equipo. + +Web Application Prototype + +**Enlace al prototipo:** + +El prototipo interactivo de la aplicación web de Reqs-AI se encuentra disponible en el siguiente enlace: [Reqs-AI Web Application Prototype](https://www.figma.com/proto/UVAwp3YUbl7HdyW36b40d6/Web-application-prototype?node-id=25-16791&p=f&t=eUjvMWP8QbWcmZbl-1&scaling=min-zoom&content-scaling=fixed&page-id=0%3A1&starting-point-node-id=25%3A16791&show-proto-sidebar=1) + +El video del prototipo interactivo se encuentra disponible en el siguiente enlace: [Reqs-AI Web Application Prototype Video](https://upcedupe-my.sharepoint.com/:v:/g/personal/u202319668_upc_edu_pe/IQDz-JGNFecGR5wGEjgFKkb1Ab_PQSFGMfoLZ4QStDYVWiw?nav=eyJyZWZlcnJhbEluZm8iOnsicmVmZXJyYWxBcHAiOiJPbmVEcml2ZUZvckJ1c2luZXNzIiwicmVmZXJyYWxBcHBQbGF0Zm9ybSI6IldlYiIsInJlZmVycmFsTW9kZSI6InZpZXciLCJyZWZlcnJhbFZpZXciOiJNeUZpbGVzTGlua0NvcHkifX0&e=ThYQDY) + +**Mobile Application Prototyping** + +En esta sección se presentan el prototipo interactivo de alta fidelidad para la aplicación móvil de Reqs-AI, construido con Figma. El prototipo integra los Mock-ups finales en una experiencia navegable que simula el comportamiento real del producto digital, permitiendo validar la usabilidad, la consistencia visual y la fluidez de las transiciones entre pantallas. + +![Mobile Application Prototype](./assets/ui/mobile/prototype/mobile-application-prototype.png) + +**Enlace al prototipo:** + +El prototipo interactivo de la aplicación móvil de Reqs-AI se encuentra disponible en el siguiente enlace: [Reqs-AI Mobile Application Prototype](https://www.figma.com/proto/TBYTXyq5REHaJd57XdMFe3/Mobile-App?node-id=10-275&viewport=-1730%2C-1243%2C0.17&t=4JoIIEahuM82Ubyn-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=10%3A275&show-proto-sidebar=1&page-id=0%3A1) + +El video del prototipo interactivo se encuentra disponible en el siguiente enlace: [Reqs-AI Mobile Application Prototype Video](https://upcedupe-my.sharepoint.com/:v:/g/personal/u20201e843_upc_edu_pe/IQAQ5aiOb23nSI7phtra3Un_AQX1hqL_qhVPW8CldVvTLv0?nav=eyJyZWZlcnJhbEluZm8iOnsicmVmZXJyYWxBcHAiOiJPbmVEcml2ZUZvckJ1c2luZXNzIiwicmVmZXJyYWxBcHBQbGF0Zm9ybSI6IldlYiIsInJlZmVycmFsTW9kZSI6InZpZXciLCJyZWZlcnJhbFZpZXciOiJNeUZpbGVzTGlua0NvcHkifX0&e=BmdCkh) + +# Capítulo VII: Product Implementation, Validation & Deployment + +## 7.1. Software Configuration Management + +### 7.1.1. Software Development Environment Configuration + +### 7.1.2. Source Code Management + +### 7.1.3. Source Code Style Guide & Conventions + +### 7.1.4. Software Deployment Configuration + +## 7.2. Solution Implementation + +### 7.2.X. Sprint n + +#### 7.2.X.1. Sprint Planning n + +#### 7.2.X.2. Sprint Backlog n + +#### 7.2.X.3. Development Evidence for Sprint Review + +#### 7.2.X.4. Testing Suite Evidence for Sprint Review + +#### 7.2.X.5. Execution Evidence for Sprint Review + +#### 7.2.X.6. Services Documentation Evidence for Sprint Review + +#### 7.2.X.7. Software Deployment Evidence for Sprint Review + +#### 7.2.X.8. Team Collaboration Insights during Sprint + +## 7.3. Validation Interviews + +### 7.3.1. Diseño de Entrevistas + +### 7.3.2. Registro de Entrevistas + +### 7.3.3. Evaluaciones según heurísticas + +## 7.4. Video About-the-Product + +# Conclusiones + +El equipo concluye que el problema abordado es real, recurrente y de alto impacto en el ciclo de vida del software: la ambigüedad en el levantamiento de requisitos y la sobrecarga de postprocesamiento generan retrabajo, retrasos y riesgo de construir funcionalidades incorrectas. La evidencia obtenida en entrevistas confirma un patrón consistente en ambos segmentos objetivo (Líder Técnico de Startup y Analista de Sistemas/Producto): transformar conversaciones en requisitos claros, trazables y accionables sigue siendo el principal cuello de botella. + +Sobre esta base, Reqs-AI se consolida como una propuesta de valor pertinente al combinar asistencia en tiempo real, generación estructurada de historias de usuario y criterios de aceptación, y mecanismos de integración con herramientas de gestión del backlog. El enfoque del producto no reemplaza el criterio profesional del analista o líder técnico, sino que lo potencia para reducir omisiones, acelerar la claridad funcional y mejorar la calidad de entrada hacia desarrollo y QA. + +Asimismo, el trabajo desarrollado en el informe demuestra coherencia metodológica entre descubrimiento, análisis y diseño de solución. Los artefactos de Lean UX, entrevistas, need finding, user stories, backlog e impact mapping se enlazan con decisiones arquitectónicas estratégicas (DDD, EventStorming, Bounded Contexts y lineamientos de seguridad multitenancy con RLS), aportando trazabilidad desde la necesidad del usuario hasta la estructura técnica propuesta. + +Respecto a las hipótesis planteadas, el equipo considera que cuentan con validación inicial de problema y de deseabilidad, debido a la convergencia de hallazgos cualitativos y cuantitativos en las entrevistas. Sin embargo, su validación de desempeño y negocio permanece parcial, ya que métricas objetivas como reducción de reuniones de aclaración, tiempo de edición manual por sesión, retención de uso y sincronización efectiva al backlog deben medirse con el producto en operación real. + +La principal limitación actual del proyecto es que aún no se presenta evidencia completa de implementación, pruebas de campo y resultados longitudinales de adopción. En consecuencia, aunque la arquitectura y el diseño funcional están sólidamente fundamentados, todavía es necesario contrastar el comportamiento del sistema en escenarios productivos con usuarios reales y condiciones de carga, seguridad y dependencia de servicios externos de IA. + +Como siguientes pasos, se recomienda priorizar un MVP enfocado en el flujo crítico end-to-end (captura de reunión, síntesis guiada, generación de historias con criterios de aceptación y exportación a backlog), ejecutar pilotos controlados en startups y entornos enterprise, y definir un tablero de métricas para validar hipótesis de valor, eficiencia y confianza. Con ello, Reqs-AI podrá transitar de una solución bien diseñada en el plano estratégico a una plataforma validada en impacto operativo y escalabilidad de negocio. + +# Bibliografía + +>Pulse of the Profession (2018) Success in Disruptive Times. Project Management Institute. Recuperado el 15 de Abril de 2025, de https://www.pmi.org/learning/thought-leadership/pulse/pulse-of-the-profession-2018 + +>Jhonson J (2020) CHAOS Report: Beyond Infinity. Standish Group. Recuperado el 15 de Abril de 2025, de https://www.standishgroup.com/products/copy-of-chaos-report-beyond-infinity-digital-version # Anexos diff --git a/assets/architecture/container-diagram.png b/assets/architecture/container-diagram.png deleted file mode 100644 index b9186fc..0000000 Binary files a/assets/architecture/container-diagram.png and /dev/null differ diff --git a/assets/architecture/deployment-diagram.png b/assets/architecture/deployment-diagram.png deleted file mode 100644 index 8535983..0000000 Binary files a/assets/architecture/deployment-diagram.png and /dev/null differ diff --git a/assets/architecture/system-context.png b/assets/architecture/system-context.png deleted file mode 100644 index 0700784..0000000 Binary files a/assets/architecture/system-context.png and /dev/null differ diff --git a/assets/architecture/system-landscape.png b/assets/architecture/system-landscape.png deleted file mode 100644 index b85c2fa..0000000 Binary files a/assets/architecture/system-landscape.png and /dev/null differ diff --git a/assets/ddd/bounded-contexts.jpg b/assets/ddd/bounded-contexts.jpg index c0771b2..3f77c31 100644 Binary files a/assets/ddd/bounded-contexts.jpg and b/assets/ddd/bounded-contexts.jpg differ diff --git a/assets/diagrams/architecture/container-diagram.png b/assets/diagrams/architecture/container-diagram.png new file mode 100644 index 0000000..3721985 Binary files /dev/null and b/assets/diagrams/architecture/container-diagram.png differ diff --git a/assets/diagrams/architecture/container-diagram.puml b/assets/diagrams/architecture/container-diagram.puml new file mode 100644 index 0000000..e83ac8d --- /dev/null +++ b/assets/diagrams/architecture/container-diagram.puml @@ -0,0 +1,90 @@ +@startuml container-diagram +!include ../c4/C4_Container.puml +!include ../c4/C4_Component.puml +!include ../styles/reqsai-palette.puml + +HIDE_PERSON_SPRITE() + +skinparam defaultFontName "Segoe UI" +skinparam shadowing false + +UpdateElementStyle(person, $bgColor=$REQSAI_NAVY_800, $fontColor="#FFFFFF", $borderColor="#1A3A5C") +UpdateElementStyle(system_ext, $bgColor="#455A64", $fontColor="#FFFFFF", $borderColor="#263238") +UpdateElementStyle(container, $bgColor=$REQSAI_BLUE_700, $fontColor="#FFFFFF", $borderColor=$REQSAI_BLUE_800) +UpdateElementStyle(containerDb, $bgColor=$REQSAI_TEAL_800, $fontColor="#FFFFFF", $borderColor=$REQSAI_TEAL_900) +UpdateElementStyle(component, $bgColor=$REQSAI_BLUE_600, $fontColor="#FFFFFF", $borderColor=$REQSAI_BLUE_700) + +AddContainerTag("edge", $bgColor="#37474F", $fontColor="#FFFFFF", $borderColor="#263238", $legendText="Edge / Routing Layer (AWS)") +AddContainerTag("frontend", $bgColor=$REQSAI_NAVY_800, $fontColor="#FFFFFF", $borderColor="#1A3A5C", $legendText="Frontend Application") + +title Container Diagram -- ReqsAI (C4 Level 2) + +' ── Actors ────────────────────────────────────────────────────────── +Person(techLead, "Technical Lead", "Inicia grabaciones y\naprueba historias de usuario.") +Person(analyst, "Enterprise Analyst", "Administra la organizacion\ny el plan de suscripcion.") + +' ── Edge layer ────────────────────────────────────────────────────── +Container(cdn, "CDN & Reverse Proxy", "Amazon CloudFront", "Sirve los activos estaticos del SPA Angular\ndesde ubicaciones globales (Edge Locations),\ncachea respuestas, mitiga DDoS y enruta\nel trafico de API hacia el API Gateway.", $tags="edge") +Container(apiGw, "API Gateway", "AWS API Gateway", "Punto de entrada unificado para peticiones\nREST y WebSocket desde web y mobile.\nGestiona throttling, metricas y SSL.", $tags="edge") + +' ── Frontends ──────────────────────────────────────────────────────── +Container(web, "Web Application", "Angular", "SPA web principal. Revision de historias,\nconfiguracion de proyectos y administracion\nde la organizacion. Servida por CloudFront.", $tags="frontend") +Container(mobile, "Mobile App", "Flutter", "App movil multiplataforma (iOS/Android).\nGrabacion de reuniones y revision\nde requerimientos. Llama al API Gateway\ndirectamente (no pasa por CDN).", $tags="frontend") + +' ── Backend: Monolito Modular ───────────────────────────────────────── +Container_Boundary(backend, "ReqsAI Backend Service [Java 25 + Spring Boot 4 -- Monolito Modular / Spring Modulith]") { + + Component(iam, "IAM", "Spring Modulith Module", "Autenticacion JWT, registro de cuentas,\nverificacion de email, sesiones y\ngestion de perfil de usuario.") + + Component(billing, "Billing & Subscriptions", "Spring Modulith Module", "Ciclo de vida de suscripciones, control de\ncuotas de tokens e integracion con\nla pasarela de pagos externa.") + + Component(workspace, "Workspace Management", "Spring Modulith Module", "Organizaciones, proyectos, miembros, roles,\ndocumentos, glosarios y aplicacion de\nlimites de plan (Row Level Security).") + + Component(discovery, "Requirement Discovery", "Spring Modulith Module", "Captura de audio, transcripcion STT,\nmotor RAG con embeddings y generacion\nde historias Gherkin via LLM.") + + Component(gateway, "Integration Gateway", "Spring Modulith Module", "Exportacion de historias de usuario\naprobadas a herramientas externas\nde gestion de proyectos.") +} + +' ── Database ───────────────────────────────────────────────────────── +ContainerDb(db, "Database", "PostgreSQL + pgvector\n[AWS RDS]", "Base de datos relacional administrada.\nLa extension pgvector habilita almacenamiento\nde embeddings vectoriales para el motor RAG.") + +' ── External Systems ───────────────────────────────────────────────── +System_Ext(email, "Email Service Provider", "Correos transaccionales:\nverificacion, recuperacion, invitaciones.") +System_Ext(payment, "Payment Gateway", "Procesamiento de pagos B2B.\nEj: Stripe, Culqi, Mercado Pago.") +System_Ext(stt, "STT API", "Speech-to-Text: convierte audio\nen texto en tiempo real (< 2 seg).") +System_Ext(embedApi, "Embedding API", "Convierte texto en vectores para\nel indice RAG (pgvector). Ej: OpenAI\ntext-embedding-3-small, Cohere Embed.") +System_Ext(llm, "LLM API", "Large Language Model generativo.\nEj: GPT-4o, Claude, Gemini.") +System_Ext(pmtool, "Project Management API", "Crea issues automaticamente\nen el backlog. Ej: Jira, Trello, Linear.") + +' ── Relations ──────────────────────────────────────────────────────── +' Web: pasa por CDN +Rel(techLead, cdn, "Accede via navegador", "HTTPS") +Rel(analyst, cdn, "Accede via navegador", "HTTPS") +Rel(cdn, web, "Sirve SPA estatico", "HTTPS") +Rel(cdn, apiGw, "Enruta llamadas API", "HTTPS") +Rel(web, apiGw, "Llamadas API", "HTTPS / REST") + +' Mobile: directo al API Gateway (app instalada desde App Store, no desde CDN) +Rel(techLead, mobile, "Usa app movil", "App Store / Play Store") +Rel(mobile, apiGw, "Llamadas API directas","HTTPS / REST + WebSocket") + +Rel(apiGw, backend, "Enruta peticiones al monolito", "HTTPS / REST") + +Rel(iam, db, "Lee y escribe", "JDBC / JPA") +Rel(billing, db, "Lee y escribe", "JDBC / JPA") +Rel(workspace, db, "Lee y escribe", "JDBC / JPA") +Rel(discovery, db, "Lee y escribe", "JDBC / JPA") +Rel(gateway, db, "Lee y escribe", "JDBC / JPA") + +Rel(iam, email, "Envia correos transaccionales via", "REST API") +Rel(billing, payment, "Procesa pagos de suscripcion via", "REST API") +Rel(workspace, embedApi, "Vectoriza docs + glosario para RAG via", "REST API") +Rel(workspace, llm, "Procesa contenido de documentos via", "REST API") +Rel(discovery, stt, "Envia audio para transcripcion via", "REST / Streaming") +Rel(discovery, embedApi, "Vectoriza user stories generadas via", "REST API") +Rel(discovery, llm, "Infiere historias en Gherkin via", "REST API") +Rel(gateway, pmtool, "Exporta historias aprobadas via", "REST API") + +SHOW_LEGEND() + +@enduml diff --git a/assets/diagrams/architecture/deployment-diagram.png b/assets/diagrams/architecture/deployment-diagram.png new file mode 100644 index 0000000..8953e89 Binary files /dev/null and b/assets/diagrams/architecture/deployment-diagram.png differ diff --git a/assets/diagrams/architecture/deployment-diagram.puml b/assets/diagrams/architecture/deployment-diagram.puml new file mode 100644 index 0000000..be33fb1 --- /dev/null +++ b/assets/diagrams/architecture/deployment-diagram.puml @@ -0,0 +1,128 @@ +@startuml deployment-diagram +!include ../styles/reqsai-palette.puml + +skinparam defaultFontName "Segoe UI" +skinparam shadowing false +skinparam backgroundColor #FFFFFF +skinparam ArrowColor $REQSAI_ARROW +skinparam ArrowFontColor $REQSAI_NEUTRAL_800 +skinparam ArrowFontSize 11 + +skinparam node { + BackgroundColor $REQSAI_NEUTRAL_100 + BorderColor $REQSAI_NEUTRAL_600 + FontColor $REQSAI_NEUTRAL_800 + FontSize 12 + FontStyle bold +} + +skinparam artifact { + BackgroundColor $REQSAI_BLUE_700 + BorderColor $REQSAI_BLUE_900 + FontColor #FFFFFF + FontSize 11 +} + +skinparam database { + BackgroundColor $REQSAI_TEAL_800 + BorderColor $REQSAI_TEAL_900 + FontColor #FFFFFF + FontSize 11 +} + +skinparam component { + BackgroundColor $REQSAI_NAVY_800 + BorderColor "#1A3A5C" + FontColor #FFFFFF + FontSize 11 +} + + +title Deployment Diagram -- ReqsAI (C4 Level 4) + +' ──────────────────────────────────────────────────────────────────── +' CLIENT SIDE +' ──────────────────────────────────────────────────────────────────── +node "Mobile Devices [iOS / Android]" as mobileDevices { + node "User Smartphone" as smartphone { + artifact "Mobile App\n[Flutter Engine]" as mobileApp + } +} + +node "User Workstations [Windows / macOS / Linux]" as workstations { + node "Web Browser [Browser Runtime]" as browser { + artifact "Web Application\n[Angular SPA]" as webApp + } +} + +' ──────────────────────────────────────────────────────────────────── +' AWS EDGE LOCATION +' ──────────────────────────────────────────────────────────────────── +node "AWS Edge Location [Amazon CloudFront]" as edgeLayer { + artifact "CDN & Reverse Proxy\n[Amazon CloudFront]\nSirve SPA estatico + enruta API" as cdn +} + +' ──────────────────────────────────────────────────────────────────── +' AWS NORTH AMERICA (us-east-1 — Virginia) +' ──────────────────────────────────────────────────────────────────── +node "AWS North America [AWS Region us-east-1 (Virginia)]" as awsNorthAmerica { + + node "API Gateway [AWS API Gateway]" as apiGatewayNode { + artifact "API Gateway\n[AWS API Gateway]\nRouting REST + WebSocket" as apiGw + } + + node "ECS Cluster [AWS ECS + Fargate]" as ecsCluster { + component "ReqsAI Backend Service\n[Java 25 + Spring Boot 4 / Docker Container]\n\nModulos: IAM · Billing · Workspace\nRequirement Discovery · Integration Gateway" as backend + artifact "Grafana Alloy\n[Sidecar Container]\nColecta metricas, logs y trazas\ndel backend y los envia al\nservidor de observabilidad" as alloy + } + + node "Observability Server [AWS EC2 + Docker Compose]" as obsServer { + component "Prometheus\n[Metricas]" as prometheus + component "Loki\n[Logs]" as loki + component "Tempo\n[Trazas distribuidas]" as tempo + component "Grafana\n[Dashboards]" as grafana + } +} + +' ──────────────────────────────────────────────────────────────────── +' AWS RDS +' ──────────────────────────────────────────────────────────────────── +node "AWS RDS [Managed Relational Database]" as dbNode { + database "PostgreSQL + pgvector\n[ReqsAI Main Database]" as db +} + +' ──────────────────────────────────────────────────────────────────── +' RELATIONS +' ──────────────────────────────────────────────────────────────────── +' Web: pasa por CloudFront +webApp --> cdn : "HTTPS" +cdn --> webApp : "Sirve SPA estatico\n(Edge Locations)" +cdn --> apiGw : "Enruta trafico API\nHTTPS" + +' Mobile: directo al API Gateway (app instalada, no servida por CDN) +mobileApp --> apiGw : "Llamadas API directas\nHTTPS / REST + WebSocket" + +apiGw --> backend : "Route all requests\nHTTPS / REST" + +' Observabilidad: Alloy como sidecar recolecta del backend +backend --> alloy : "Metricas /actuator/prometheus\nLogs stdout · Trazas OTLP" +alloy --> prometheus : "Push metricas\nRemote Write" +alloy --> loki : "Push logs" +alloy --> tempo : "Push trazas\nOTLP" +grafana --> prometheus : "Query metricas\nPromQL" +grafana --> loki : "Query logs\nLogQL" +grafana --> tempo : "Query trazas\nTraceQL" + +' Persistencia +backend --> db : "Reads and writes\nJDBC / JPA" + +legend bottom right + |= Elemento |= Descripcion | + | Nodo | Infraestructura / entorno de despliegue | + | Artefacto | Aplicacion o servicio desplegado | + | Componente | Modulo de aplicacion (Spring Modulith) | + | Base de datos | Almacenamiento relacional (PostgreSQL) | + | -> | Comunicacion entre nodos | +endlegend + +@enduml diff --git a/assets/diagrams/architecture/system-context.png b/assets/diagrams/architecture/system-context.png new file mode 100644 index 0000000..5ea20cc Binary files /dev/null and b/assets/diagrams/architecture/system-context.png differ diff --git a/assets/diagrams/architecture/system-context.puml b/assets/diagrams/architecture/system-context.puml new file mode 100644 index 0000000..89ae496 --- /dev/null +++ b/assets/diagrams/architecture/system-context.puml @@ -0,0 +1,42 @@ +@startuml system-context +!include ../c4/C4_Context.puml +!include ../styles/reqsai-palette.puml + +HIDE_PERSON_SPRITE() + +skinparam defaultFontName "Segoe UI" +skinparam shadowing false + +UpdateElementStyle(person, $bgColor=$REQSAI_NAVY_800, $fontColor="#FFFFFF", $borderColor="#1A3A5C") +UpdateElementStyle(system, $bgColor=$REQSAI_BLUE_800, $fontColor="#FFFFFF", $borderColor=$REQSAI_BLUE_900) +UpdateElementStyle(system_ext, $bgColor="#455A64", $fontColor="#FFFFFF", $borderColor="#263238") + +title System Context Diagram -- ReqsAI (C4 Level 1) + +Person(techLead, "Technical Lead", "Inicia grabaciones de reuniones en\ntiempo real, aprueba las historias de\nusuario generadas por IA y gestiona\nel backlog del proyecto.") +Person(analyst, "Enterprise Analyst", "Administra la organizacion corporativa,\ncarga glosarios tecnicos, gestiona el\nplan de suscripcion B2B y revisa\nlos reportes de consumo.") + +System(reqsai, "ReqsAI System", "Plataforma que ingiere audio de reuniones en tiempo real,\naplicando IA con contexto RAG (glosario + documentos del\ncliente) para generar historias de usuario estructuradas\nen formato Gherkin, exportandolas a herramientas agiles.") + +System_Ext(email, "Email Service Provider", "Servicio cloud de correo electronico.\nEnvia correos transaccionales: verificacion\nde cuenta, recuperacion de contrasena\ne invitaciones a la organizacion.") +System_Ext(payment, "Payment Gateway", "Procesamiento de transacciones de\nsuscripciones B2B corporativas.\nEj: Stripe, Culqi, Mercado Pago.") +System_Ext(stt, "STT API", "Speech-to-Text API. Convierte el\nstreaming de audio de las reuniones\nen texto con latencia < 2 seg.") +System_Ext(embedApi, "Embedding API", "Convierte texto en vectores numericos\npara el indice RAG. Usado por Workspace\n(documentos + glosario) y Discovery\n(user stories generadas).") +System_Ext(llm, "LLM API", "Large Language Model generativo.\nUsado por Workspace (procesa contenido\nde documentos) y Discovery\n(genera historias en formato Gherkin).") +System_Ext(pmtool, "Project Management API", "Crea issues en el backlog del\nequipo de forma automatica a partir\nde las historias aprobadas. Ej: Jira,\nTrello, Linear.") + +' ── Inbound ───────────────────────────────────────────────────────── +Rel(techLead, reqsai, "Usa", "HTTPS / WebSocket") +Rel(analyst, reqsai, "Usa", "HTTPS") + +' ── Outbound ──────────────────────────────────────────────────────── +Rel(reqsai, email, "Envia correos via", "REST API") +Rel(reqsai, payment, "Procesa pagos de suscripcion via","REST API") +Rel(reqsai, stt, "Envia audio para transcripcion via", "REST / Streaming") +Rel(reqsai, embedApi, "Vectoriza texto para indice RAG via", "REST API") +Rel(reqsai, llm, "Procesa docs e infiere Gherkin via", "REST API") +Rel(reqsai, pmtool, "Exporta historias aprobadas via", "REST API") + +SHOW_LEGEND() + +@enduml diff --git a/assets/diagrams/architecture/system-landscape.png b/assets/diagrams/architecture/system-landscape.png new file mode 100644 index 0000000..cc69f69 Binary files /dev/null and b/assets/diagrams/architecture/system-landscape.png differ diff --git a/assets/diagrams/architecture/system-landscape.puml b/assets/diagrams/architecture/system-landscape.puml new file mode 100644 index 0000000..68261e3 --- /dev/null +++ b/assets/diagrams/architecture/system-landscape.puml @@ -0,0 +1,65 @@ +@startuml system-landscape +!include ../c4/C4_Context.puml +!include ../styles/reqsai-palette.puml + +HIDE_PERSON_SPRITE() + +skinparam defaultFontName "Segoe UI" +skinparam shadowing false + +UpdateElementStyle(person, $bgColor=$REQSAI_NAVY_800, $fontColor="#FFFFFF", $borderColor="#1A3A5C") +UpdateElementStyle(system, $bgColor=$REQSAI_BLUE_800, $fontColor="#FFFFFF", $borderColor=$REQSAI_BLUE_900) +UpdateElementStyle(system_ext, $bgColor="#455A64", $fontColor="#FFFFFF", $borderColor="#263238") + +AddSystemTag("videoconf", $bgColor="#1B5E20", $fontColor="#FFFFFF", $borderColor="#0A3A0F", $legendText="Herramienta de videconferencia del cliente") + +title System Landscape Diagram -- ReqsAI (C4 Level 0) + +Enterprise_Boundary(kntrosoft, "Kntro-Soft Enterprise") { + Person(techLead, "Technical Lead", "Ingeniero senior con responsabilidades\nde liderazgo tecnico. Inicia grabaciones,\naprueba historias de usuario\ny gestiona el backlog del equipo.") + Person(analyst, "Enterprise Analyst", "Analiza datos y procesos de la\norganizacion. Administra el workspace\ncorporativo, carga glosarios tecnicos\ny gestiona el plan de suscripcion B2B.") + System(reqsai, "ReqsAI System", "Plataforma que ingiere audio de reuniones\nen tiempo real, aplica IA con contexto RAG\ny genera historias de usuario estructuradas,\nexportandolas a herramientas agiles.") +} + +' ── Herramientas del ecosistema del cliente ────────────────────────── +System_Ext(videoconf, "Video Conferencing Tool", "Plataforma de videoconferencia corporativa\n(Google Meet, Zoom, MS Teams). Los usuarios\nrealizan sus reuniones de levantamiento\nde requisitos en esta herramienta y exportan\nlas grabaciones para subirlas a ReqsAI.", $tags="videoconf") + +System_Ext(jira, "Project Management Tool", "Herramienta de gestion de proyectos\n(Jira, Trello, Linear). Recibe las historias\nde usuario aprobadas como issues\ndel backlog del equipo.") +System_Ext(email, "Email Service Provider", "Servicio cloud de correo electronico.\nEnvia verificaciones de cuenta\ne invitaciones a la organizacion.") +System_Ext(sup,"Customer Support Channel", "Canal para el servicio de atención al cliente\n via correo por los usuarios.") + + +' ── Servicios tecnologicos de soporte ──────────────────────────────── +System_Ext(payment, "Payment Gateway", "Pasarela de pagos B2B corporativa\n(Stripe, Culqi). Procesa suscripciones.") +System_Ext(stt, "STT API", "Speech-to-Text API. Convierte el audio\nde las reuniones en texto en tiempo real.") +System_Ext(embedApi, "Embedding API", "Convierte texto en vectores numericos\npara el motor RAG. Usado por Workspace\n(docs + glosario) y Discovery\n(user stories generadas).") +System_Ext(llm, "LLM API", "Large Language Model generativo. Procesa\ncontenido de documentos en Workspace y\ngenera historias Gherkin en Discovery.") + +' ── Landscape effect: usuarios -> herramientas propias ─────────────── +Rel(techLead, videoconf, "Realiza reuniones de\nlevantamiento de requisitos") +Rel(analyst, videoconf, "Participa en reuniones\nde elicitacion") +Rel(techLead, jira, "Gestiona sprints y rastrea\nhistorias exportadas directamente") +Rel(analyst, email, "Recibe invitaciones de\norganizacion y verificaciones") + +' ── Usuarios -> ReqsAI ─────────────────────────────────────────────── +Rel(techLead, reqsai, "Sube grabaciones y\naprueba historias", "HTTPS") +Rel(analyst, reqsai, "Administra workspace\ny suscripcion", "HTTPS") + +' ── Landscape effect: videoconf -> ReqsAI ──────────────────────────── +Rel(videoconf, reqsai, "Provee grabaciones de audio\n(subida manual por el usuario)") + +' ── Soporte al Cliente ─────────────────────────────────────────────── +Rel(techLead, sup, "Envía consultas y reporta problemas técnicos\nvía correo de soporte") +Rel(analyst, sup, "Envía consultas y reporta problemas de facturación\nvía correo de soporte") + +' ── ReqsAI -> sistemas externos ────────────────────────────────────── +Rel(reqsai, jira, "Exporta historias\naprobadas automaticamente") +Rel(reqsai, email, "Envia correos transaccionales\nvia REST API") +Rel(reqsai, payment, "Procesa pagos de\nsuscripcion") +Rel(reqsai, stt, "Envia audio para\ntranscripcion en tiempo real") +Rel(reqsai, embedApi, "Vectoriza documentos,\nglosario y user stories") +Rel(reqsai, llm, "Procesa documentos\ne infiere historias Gherkin") + +SHOW_LEGEND() + +@enduml diff --git a/assets/diagrams/billing/billing-class.png b/assets/diagrams/billing/billing-class.png new file mode 100644 index 0000000..fc9cc58 Binary files /dev/null and b/assets/diagrams/billing/billing-class.png differ diff --git a/assets/diagrams/billing/billing-class.puml b/assets/diagrams/billing/billing-class.puml new file mode 100644 index 0000000..469e7c4 --- /dev/null +++ b/assets/diagrams/billing/billing-class.puml @@ -0,0 +1,180 @@ +@startuml billing-class +!include ../styles/reqsai-class-style.puml + +top to bottom direction + +title Billing BC -- Tactical Class Diagram + +legend top right + |= Elemento |= Significado | + |<$REQSAI_NAVY_800> <> | Raiz de aggregate | + |<$REQSAI_GREEN_900> <> | Record inmutable y validado | + |<$REQSAI_VIOLET_900> <> | Enumeracion de dominio | + |<$REQSAI_BROWN_900> <> | Evento de dominio (inmutable) | + |<$REQSAI_GRAY_700> <> | Clase abstracta Shared Kernel | + |Relacion|Significado| + | --\\|> | extends (herencia) | + | ..> | uses / depends on | + | o-- | referencia por ID (cross-aggregate) | + | ..> <>| evento publicado por el aggregate | +endlegend + +package "Shared Kernel" as SharedKernel { + abstract class AbstractAggregateRoot <> + abstract class EntityNotFoundException <> + abstract class BusinessRuleViolationException <> +} + +package "Billing" { + + package "Domain" { + package "Model" { + package "Aggregates" as BillingAggregates { + + class Subscription <> { + - id : SubscriptionId + - organizationId : OrganizationId + - planType : PlanType + - status : SubscriptionStatus + - providerRef : PaymentProviderRef + - currentPeriodStart : Instant + - currentPeriodEnd : Instant + - tokenQuotaUsed : Long + - cancelledAt : Instant + -- + # Subscription() + + Subscription(organizationId : OrganizationId) + ..Business Methods.. + + upgradeTo(planType : PlanType, providerRef : PaymentProviderRef, periodStart : Instant, periodEnd : Instant) : void + + cancel(cancelledAt : Instant) : void + + reactivate(periodStart : Instant, periodEnd : Instant) : void + + incrementTokenUsage(tokens : Long) : void + + resetQuota() : void + + applyProviderRef(providerRef : PaymentProviderRef) : void + ..Query Methods.. + + isActive() : boolean + + isCancelled() : boolean + + isPastDue() : boolean + + isQuotaExceeded(maxTokens : Long) : boolean + + isFree() : boolean + } + } + } + } + + package "Domain" { + package "Model" { + package "Value Objects" as BillingValueObjects { + class SubscriptionId <> + class OrganizationId <> + + class PaymentProviderRef <> { + - provider : PaymentProvider + - externalId : String + -- + + PaymentProviderRef(provider : PaymentProvider, externalId : String) + + provider() : PaymentProvider + + externalId() : String + } + + enum PlanType <> { + FREE + PRO + ENTERPRISE + } + + enum SubscriptionStatus <> { + ACTIVE + CANCELLED + PAST_DUE + TRIALING + } + + enum PaymentProvider <> { + STRIPE + CULQI + PAYPAL + MERCADO_PAGO + } + } + + package "Exceptions" as BillingExceptions { + class SubscriptionNotFoundException <> + class SubscriptionAlreadyExistsException <> + class CannotUpgradeSubscriptionException <> + class CannotCancelSubscriptionException <> + class CannotReactivateSubscriptionException <> + class TokenQuotaExceededException <> + } + } + } + + package "Api" as BillingApi { + class SubscriptionAssignedEvent <> { + + subscriptionId : String + + organizationId : String + + planType : String + + occurredAt : Instant + -- + + {static} of(subscriptionId : String, organizationId : String, planType : String) : SubscriptionAssignedEvent + } + class SubscriptionUpgradedEvent <> { + + subscriptionId : String + + organizationId : String + + oldPlan : String + + newPlan : String + + occurredAt : Instant + -- + + {static} of(subscriptionId : String, organizationId : String, oldPlan : String, newPlan : String) : SubscriptionUpgradedEvent + } + class TokenQuotaExceededEvent <> { + + subscriptionId : String + + organizationId : String + + quotaUsed : Long + + occurredAt : Instant + -- + + {static} of(subscriptionId : String, organizationId : String, quotaUsed : Long) : TokenQuotaExceededEvent + } + } + + package "Domain" { + package "Model" { + package "Events" as BillingDomainEvents { + class SubscriptionCancelledEvent <> { + + subscriptionId : String + + organizationId : String + + occurredAt : Instant + -- + + {static} of(subscriptionId : String, organizationId : String) : SubscriptionCancelledEvent + } + } + } + } +} + +' ── Inheritance ────────────────────────────────────────────────────── +Subscription --|> AbstractAggregateRoot : extends + +SubscriptionNotFoundException --|> EntityNotFoundException +SubscriptionAlreadyExistsException --|> BusinessRuleViolationException +CannotUpgradeSubscriptionException --|> BusinessRuleViolationException +CannotCancelSubscriptionException --|> BusinessRuleViolationException +CannotReactivateSubscriptionException --|> BusinessRuleViolationException +TokenQuotaExceededException --|> BusinessRuleViolationException + +' ── Dependencies ───────────────────────────────────────────────────── +Subscription ..> PlanType : uses +Subscription ..> SubscriptionStatus : uses +Subscription ..> PaymentProviderRef : uses +PaymentProviderRef ..> PaymentProvider : uses + +' ── Cross-aggregate ID references ──────────────────────────────────── +Subscription "1" o-- "1" OrganizationId : organizationId (reference) + +' ── Domain Events published ─────────────────────────────────────────── +Subscription ..> SubscriptionAssignedEvent : <> +Subscription ..> SubscriptionUpgradedEvent : <> +Subscription ..> SubscriptionCancelledEvent : <> +Subscription ..> TokenQuotaExceededEvent : <> + +@enduml diff --git a/assets/diagrams/billing/billing-component.png b/assets/diagrams/billing/billing-component.png new file mode 100644 index 0000000..deca3b0 Binary files /dev/null and b/assets/diagrams/billing/billing-component.png differ diff --git a/assets/diagrams/billing/billing-component.puml b/assets/diagrams/billing/billing-component.puml new file mode 100644 index 0000000..352efef --- /dev/null +++ b/assets/diagrams/billing/billing-component.puml @@ -0,0 +1,63 @@ +@startuml billing-component +!include ../c4/C4_Container.puml +!include ../c4/C4_Component.puml +!include ../styles/reqsai-c4-component-style.puml + +' ── Billing-specific external tag ────────────────────────────────── +AddSystemTag("externalPayment", $bgColor="#37474F", $fontColor="#FFFFFF", $borderColor="#1C2B33", $legendText="External Payment Gateway") + +title Component Diagram -- Billing Bounded Context (C4 Level 3) + +' ── External actors ────────────────────────────────────────────────── +Person(admin, "Administrador", "Gestiona planes y suscripciones\ndel workspace.") +System_Ext(wsBC, "Workspace BC", "Verifica limites del plan activo\nvía Module API.", $tags="externalBC") +System_Ext(gateway, "Payment Gateway","Stripe / Culqi: procesa pagos\ny gestiona suscripciones externas.", $tags="externalPayment") +ContainerDb(postgres,"PostgreSQL", "Database", "plans · subscriptions", $tags="database") + +' ── Billing Module boundary ────────────────────────────────────────── +Container_Boundary(billing, "Billing [Spring Modulith Module]") { + + ' -- Interface Layer -- + Component(planCtrl, "PlanController", "REST Controller", "GET /plans\nGET /plans/{id}", $tags="interface") + Component(subscriptionCtrl, "SubscriptionController", "REST Controller", "GET /subscriptions/current\nPOST /subscriptions\nPATCH /subscriptions/current/plan\nDELETE /subscriptions/current", $tags="interface") + + ' -- Application Layer: Command Handlers -- + Component(subCmdHandlers, "Subscription Command Handlers", "CreateSubscriptionCommandHandler\nCancelSubscriptionCommandHandler\nChangePlanCommandHandler\nRenewSubscriptionCommandHandler", "Gestiona ciclo de vida de la\nsuscripcion del workspace.", $tags="application") + + ' -- Application Layer: Query Handlers -- + Component(queryHandlers, "Query Handlers", "GetCurrentSubscriptionQueryHandler\nGetAllPlansQueryHandler\nGetPlanByIdQueryHandler", "Consultas de lectura de planes\ny suscripcion activa.\n@Transactional(readOnly = true)", $tags="application") + + ' -- Domain Layer -- + Component(domain, "Domain Model", "Plan · Subscription\nPlanLimits · PaymentProviderRef · Money\nPlanType · SubscriptionStatus · BillingCycle · PaymentProvider", "Logica de negocio pura: invariantes\ny reglas del dominio de facturacion.", $tags="domain") + + ' -- Infrastructure Layer -- + Component(repos, "JPA Repositories", "PlanRepository\nSubscriptionRepository", "Implementan los repository ports.\nAcceso directo JPA sobre aggregates.", $tags="infrastructure") + Component(adapters, "Payment Adapter", "StripePaymentGatewayAdapter\nCulqiPaymentGatewayAdapter", "Implementan el payment gateway port.\nCrea y cancela suscripciones externas.", $tags="infrastructure") + + ' -- Module API -- + Component(moduleApi, "BillingModuleApiImpl", "Module API Facade", "Expone checkPlanLimits(workspaceId)\ny getCurrentPlan(workspaceId)\na otros BCs.", $tags="moduleApi") +} + +' ── External relations ─────────────────────────────────────────────── +Rel(admin, planCtrl, "HTTP REST", "JSON / HTTPS") +Rel(admin, subscriptionCtrl, "HTTP REST", "JSON / HTTPS") +Rel(wsBC, moduleApi, "In-process", "Spring Bean call") +Rel(repos, postgres, "Reads / Writes", "JDBC / Hibernate ORM") +Rel(adapters, gateway, "Payment API", "HTTPS / REST") + +' ── Internal relations ─────────────────────────────────────────────── +Rel(planCtrl, queryHandlers, "Delegates to") +Rel(subscriptionCtrl, subCmdHandlers, "Delegates to") +Rel(subscriptionCtrl, queryHandlers, "Delegates to") + +Rel(subCmdHandlers, domain, "Creates / mutates") +Rel(queryHandlers, domain, "Reads") + +Rel(subCmdHandlers, repos, "Persists via port") +Rel(subCmdHandlers, adapters, "Processes payment via port") +Rel(queryHandlers, repos, "Reads via port") + +Rel(moduleApi, repos, "Reads via port") + +SHOW_LEGEND() +@enduml diff --git a/assets/diagrams/billing/billing-database.png b/assets/diagrams/billing/billing-database.png new file mode 100644 index 0000000..9a76e54 Binary files /dev/null and b/assets/diagrams/billing/billing-database.png differ diff --git a/assets/diagrams/billing/billing-database.puml b/assets/diagrams/billing/billing-database.puml new file mode 100644 index 0000000..8d978b1 --- /dev/null +++ b/assets/diagrams/billing/billing-database.puml @@ -0,0 +1,66 @@ +@startuml billing-database +!include ../styles/reqsai-database-style.puml + +title Billing BC -- Database Design Diagram + +legend top left + |= Clave |= Significado | + |<$REQSAI_BLUE_800> T | Tabla core (entidad) | + |<>| Primary Key | + |<>| Foreign Key | + |<>| NOT NULL | + |<>| UNIQUE | + |<>| INDEX | +endlegend + +note as auditNote + Columnas de auditoria (todas las tablas) + - created_at TIMESTAMP <> + - updated_at TIMESTAMP <> + - created_by VARCHAR(36) + - updated_by VARCHAR(36) +end note + +' ── Table: subscriptions ───────────────────────────────────────────── +CORE(subscriptions) { + id : VARCHAR(36) <> + -- + organization_id : VARCHAR(36) <> + plan_type : VARCHAR(20) <> + status : VARCHAR(20) <> + current_period_start : TIMESTAMP + current_period_end : TIMESTAMP + token_quota_used : BIGINT <> + cancelled_at : TIMESTAMP + -- + payment_provider : VARCHAR(30) + payment_external_id : VARCHAR(255) + -- +} + +note right of subscriptions + organization_id + - UQ: 1 org = 1 suscripcion + - Ref logica al Workspace BC + - Sin FK fisica (BCs aislados) + + plan_type + - FREE | PRO | ENTERPRISE + + status + - ACTIVE | CANCELLED + - PAST_DUE | TRIALING + + payment_provider / payment_external_id + - Embedding del VO PaymentProviderRef + - NULL para plan FREE + - STRIPE | CULQI | PAYPAL | MERCADO_PAGO + + token_quota_used + - Tokens consumidos en el periodo actual + - Se reinicia al inicio de cada periodo +end note + +auditNote .. subscriptions + +@enduml diff --git a/assets/diagrams/c4/C4.puml b/assets/diagrams/c4/C4.puml new file mode 100644 index 0000000..513bc2c --- /dev/null +++ b/assets/diagrams/c4/C4.puml @@ -0,0 +1,1958 @@ +' C4-PlantUML + +' Global pre-settings +' ################################## +' NEW_C4_STYLE +' If NEW_C4_STYLE is set BEFORE the first C4_* file is loaded, new C4 layout style is used +' NEW_C4_STYLE can be set via +' !NEW_C4_STYLE = 1 +' or with additional command line argument -DNEW_C4_STYLE=1 +!global NEW_C4_STYLE ?= 0 + +' ROUNDED_STYLE +' If ROUNDED_STYLE is set BEFORE the first C4_* file is loaded, rectangles with rounded corners are used as default shape +' ROUNDED_STYLE can be set via +' !ROUNDED_STYLE = 1 +' or with additional command line argument -DROUNDED_STYLE=1 +!if (NEW_C4_STYLE == 1) + !global ROUNDED_STYLE ?= 1 +!else + !global ROUNDED_STYLE ?= 0 +!endif + +' ENABLE_ALL_PLANT_ELEMENTS +' If ENABLE_ALL_PLANT_ELEMENTS is set BEFORE the first C4_* file is loaded, nearly "all" PlantUML elements can be used like +' Component(StorageA, "Storage A ", $baseShape="storage") +' ENABLE_ALL_PLANT_ELEMENTS can be set via +' !ENABLE_ALL_PLANT_ELEMENTS = 1 +' or with additional command line argument -DENABLE_ALL_PLANT_ELEMENTS=1 + +' NO_LAY_ROTATE +' C4-PlantUML v2.12 fixed a missing rotation bug in Lay_* calls in combination with LAYOUT_LANDSCAPE() call +' (details see https://github.com/plantuml-stdlib/C4-PlantUML/issues/376) +' If older diagrams should remain unchanged the bugfix can be deactivated with following statement +' !NO_LAY_ROTATE = 1 +' or with follwing additional command line argument +' -DNO_LAY_ROTATE=1 +' like +' java -jar plantuml.jar -DNO_LAY_ROTATE=1 ... +!global NO_LAY_ROTATE ?= 0 + +'Version +' ################################## +!function C4Version() + ' 2 spaces and ' are used as unique marker, that the release scripts makes the correct version update + !$c4Version = "2.14.0beta1" + !return $c4Version +!end function + +!procedure C4VersionDetails() +rectangle C4VersionDetailsArea <> [ +| PlantUML | **%version()** | +| C4-PlantUML | **C4Version()** | +] +!end procedure + +' Colors +' ################################## +!$ELEMENT_FONT_COLOR ?= "#FFFFFF" + +!$ARROW_COLOR ?= "#666666" +!$ARROW_FONT_COLOR ?= $ARROW_COLOR + +!$BOUNDARY_COLOR ?= "#444444" +!$BOUNDARY_BG_COLOR ?= "transparent" +!$BOUNDARY_BORDER_STYLE ?= "dashed" +' boundary symbols written in the same line, typically only 50% of the size in element +!$BOUNDARY_IMAGE_SIZE_FACTOR ?= 0.5 +!$BOUNDARY_DESCR_MAX_CHAR_WIDTH ?= 35 + +!$LEGEND_TITLE_COLOR ?= "#000000" +!$LEGEND_FONT_COLOR ?= "#FFFFFF" +!$LEGEND_BG_COLOR ?= "transparent" +!$LEGEND_BORDER_COLOR ?= "transparent" +' %darken(darkkhaki,50), #khaki +!$LEGEND_DARK_COLOR ?= "#66622E" +!$LEGEND_LIGHT_COLOR ?= "#khaki" + +!$SKETCH_BG_COLOR ?= "#EEEBDC" +!$SKETCH_FONT_COLOR ?= "" +!$SKETCH_WARNING_COLOR ?= "red" +!$SKETCH_FONT_NAME ?= "Comic Sans MS" + +' Labels +' ################################## + +!$BOUNDARY_LEGEND_TEXT ?= "boundary" + +!$LEGEND_TITLE_TEXT ?= "Legend" +!$LEGEND_SHADOW_TEXT ?= "shadow" +!$LEGEND_NO_SHADOW_TEXT ?= "no shadow" +!$LEGEND_NO_FONT_BG_TEXT ?= "last text and back color" +!$LEGEND_NO_FONT_TEXT ?= "last text color" +!$LEGEND_NO_BG_TEXT ?= "last back color" +!$LEGEND_NO_LINE_TEXT ?= "last line color" +!$LEGEND_SHARP_CORNER ?= "box" +!$LEGEND_ROUNDED_BOX ?= "rounded box" +!$LEGEND_EIGHT_SIDED ?= "eight sided" +!$LEGEND_DOTTED_LINE ?= "dotted" +!$LEGEND_DASHED_LINE ?= "dashed" +!$LEGEND_BOLD_LINE ?= "bold" +!$LEGEND_SOLID_LINE ?= "solid" + +!$LEGEND_BOUNDARY ?= "boundary" +!$LEGEND_BOUNDARY_PRE_PART ?= "" +!$LEGEND_BOUNDARY_POST_PART ?= " " + $LEGEND_BOUNDARY + +' ignore (boundary) transparent atm, that the legend is smaller +' !$LEGEND_BOUNDARY_TRANSPARENT_INCL_COMA ?= "transparent, " +!$LEGEND_BOUNDARY_TRANSPARENT_INCL_COMA ?= "" +' (boundary) dashed should not be ignored atm +!$LEGEND_BOUNDARY_DASHED_INCL_COMA ?= "dashed, " +' !$LEGEND_BOUNDARY_DASHED_INCL_COMA ?= "" + +!$LEGEND_THICKNESS ?= "thickness" + +!$SKETCH_FOOTER_WARNING ?= "Warning:" +!$SKETCH_FOOTER_TEXT ?= "Created for discussion, needs to be validated" + +' Styling +' ################################## + +!$STEREOTYPE_FONT_SIZE ?= 12 +!global $TRANSPARENT_STEREOTYPE_FONT_SIZE = $STEREOTYPE_FONT_SIZE/2 +!$TECHN_FONT_SIZE ?= 12 + +!$ARROW_FONT_SIZE ?= 12 + +!$LEGEND_DETAILS_SMALL_SIZE ?= 10 +!$LEGEND_DETAILS_NORMAL_SIZE ?= 14 +!global $LEGEND_DETAILS_SIZE = $LEGEND_DETAILS_SMALL_SIZE + +' element symbols typically 4 times too big in legend +!$LEGEND_IMAGE_SIZE_FACTOR ?= 0.25 + +!$ROUNDED_BOX_SIZE ?= 25 +!$EIGHT_SIDED_SIZE ?= 18 + +' Default element wrap width (of an element) +!$DEFAULT_WRAP_WIDTH ?= 200 +' Maximum size in pixels, of a message (in a sequence diagram?) +!$MAX_MESSAGE_SIZE ?= 150 +' PlantUML supports no DETERMINISTIC/automatic line breaks of "PlantUML line" (C4 Relationships) +' therefore Rel...() implements an automatic line break based on spaces (like in all other objects). +' If a $type contains \n then these are used (and no automatic space based line breaks are done) +' $REL_TECHN_MAX_CHAR_WIDTH defines the automatic line break position +!$REL_TECHN_MAX_CHAR_WIDTH ?= 35 +!$REL_DESCR_MAX_CHAR_WIDTH ?= 32 + +' internal +' ################################## + +!global $SHARP_CORNER = "sharpCorner" +!global $ROUNDED_BOX = "roundedBox" +!global $EIGHT_SIDED = "eightSided" +!if (ROUNDED_STYLE == 1) + !$DEFAULT_SHAPE ?= $ROUNDED_BOX +!else + !$DEFAULT_SHAPE ?= $SHARP_CORNER +!endif + +!global $DOTTED_LINE = "dotted" +!global $DASHED_LINE = "dashed" +!global $BOLD_LINE = "bold" +' solid is not defined in plantUML, but works as reset of all other styles too +!global $SOLID_LINE = "solid" + +!global $LEGEND_DETAILS_NONE = "none" +!global $LEGEND_DETAILS_NORMAL = "normal" +!global $LEGEND_DETAILS_SMALL = "small" + +skinparam defaultTextAlignment center + +skinparam wrapWidth $DEFAULT_WRAP_WIDTH +skinparam maxMessageSize $MAX_MESSAGE_SIZE + +skinparam LegendFontColor $LEGEND_FONT_COLOR +skinparam LegendBackgroundColor $LEGEND_BG_COLOR +skinparam LegendBorderColor $LEGEND_BORDER_COLOR + +skinparam rectangle<> { + backgroundcolor $LEGEND_BG_COLOR + bordercolor $LEGEND_BORDER_COLOR +} + +skinparam rectangle { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} + +skinparam database { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} + +skinparam queue { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} + +skinparam participant { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} + +skinparam arrow { + Color $ARROW_COLOR + FontColor $ARROW_FONT_COLOR + FontSize $ARROW_FONT_SIZE +} + +skinparam person { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} + +skinparam actor { + StereotypeFontSize $STEREOTYPE_FONT_SIZE + style awesome +} + +!if %variable_exists("ENABLE_ALL_PLANT_ELEMENTS") +skinparam agent { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam artifact { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam boundary { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam card { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam circle { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam cloud { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam collections { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam control { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam entity { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam file { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam folder { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam frame { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam hexagon { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam interface { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam label { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam stack { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam storage { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam usecase { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +skinparam person { + StereotypeFontSize $STEREOTYPE_FONT_SIZE +} +!endif + +' Some boundary skinparams have to be set as package skinparams too (PlantUML uses internal packages) +' UpdateBoundaryStyle() called in boundary section below +skinparam rectangle<> { + StereotypeFontSize $TRANSPARENT_STEREOTYPE_FONT_SIZE + StereotypeFontColor $BOUNDARY_BG_COLOR + BorderStyle $BOUNDARY_BORDER_STYLE +} + +skinparam package { + StereotypeFontSize $TRANSPARENT_STEREOTYPE_FONT_SIZE + StereotypeFontColor $BOUNDARY_BG_COLOR + FontStyle plain + BackgroundColor $BOUNDARY_BG_COLOR +} + +' PlantUML compatibility utilities +' ################################## + +' PlantUML v1.2025.1beta6 introduced a new %breakline() function. +' This should be used instead of the old %newline(), if a command ends. +' (%newline() should be only used in multiline labels,...) +!function $bl() + !if (%function_exists("%breakline")) + !return %breakline() + !endif + !return %newline() +!endfunction + +' Legend and Tags +' ################################## +!global $tagDefaultLegend = "" +!global $tagCustomLegend = "" + +' rel specific +!unquoted function $toStereos($tags) + !if (%strlen($tags) == 0) + !return '' + !endif + !$stereos = '' + !$brPos = %strpos($tags, "+") + !while ($brPos >= 0) + !$tag = %substr($tags, 0, $brPos) + !$stereos = $stereos + '<<' + $tag + '>>' +%set_variable_value("$" + $tag + "_LineLegend", %true()) + !$tags = %substr($tags, $brPos+1) + !$brPos = %strpos($tags, "+") + !endwhile + !if (%strlen($tags) > 0) + !$stereos = $stereos + '<<' + $tags + '>>' +%set_variable_value("$" + $tags + "_LineLegend", %true()) + !endif + !return $stereos +!endfunction + +' if $sprite/$techn is an empty argument, try to calculate it via the defined $tag +!unquoted function $toRelArg($arg, $tags, $varPostfix) + !if ($arg > "") + !return $arg + !endif + + !if (%strlen($tags) == 0) + !return $arg + !endif + !$brPos = %strpos($tags, "+") + !while ($brPos >= 0) + !$tag = %substr($tags, 0, $brPos) + !$newArg = %get_variable_value("$" + $tag + $varPostfix) + !if ($newArg > "") + !return $newArg + !endif + !$tags = %substr($tags, $brPos+1) + !$brPos = %strpos($tags, "+") + !endwhile + !if (%strlen($tags) > 0) + !$newArg = %get_variable_value("$" + $tags + $varPostfix) + !if ($newArg > "") + !return $newArg + !endif + !endif + !return $arg +!endfunction + +' element specific (unused are hidden based on mask) +!unquoted function $toStereos($elementType, $tags) + !if (%strlen($tags) == 0) + !$stereos = '<<' + $elementType + '>>' +%set_variable_value("$" + $elementType + "Legend", %true()) + !return $stereos + !endif + !$stereos = '' + !$mask = $resetMask() + !$brPos = %strpos($tags, "+") + !while ($brPos >= 0) + !$tag = %substr($tags, 0, $brPos) + !$stereos = $stereos + '<<' + $tag + '>>' + !$mergedMask = $combineMaskWithTag($mask, $tag) + !if ($mergedMask != $mask) +%set_variable_value("$" + $tag + "Legend", %true()) + !$mask = $mergedMask + !endif + !$tags = %substr($tags, $brPos+1) + !$brPos = %strpos($tags, "+") + !endwhile + !if (%strlen($tags) > 0) + !$stereos = $stereos + '<<' + $tags + '>>' + !$mergedMask = $combineMaskWithTag($mask, $tags) + !if ($mergedMask != $mask) +%set_variable_value("$" + $tags + "Legend", %true()) + !$mask = $mergedMask + !endif + !endif + ' has to be last, otherwise PlantUML overwrites all tag specific skinparams + !$stereos = $stereos + '<<' + $elementType + '>>' + !$mergedMask = $combineMaskWithTag($mask, $elementType) + !if ($mergedMask != $mask) +%set_variable_value("$" + $elementType + "Legend", %true()) + !$mask = $mergedMask + !endif + !return $stereos +!endfunction + +' if $sprite/$techn is an empty argument, try to calculate it via the defined $tag +!unquoted function $toElementArg($arg, $tags, $varPostfix, $elementType) + !if ($arg > "") + !return $arg + !endif + + !if (%strlen($tags) == 0) + !$newArg = %get_variable_value("$" + $elementType + $varPostfix) + !if ($newArg > "") + !return $newArg + !else + !return $arg + !endif + !endif + !$brPos = %strpos($tags, "+") + !while ($brPos >= 0) + !$tag = %substr($tags, 0, $brPos) + !$newArg = %get_variable_value("$" + $tag + $varPostfix) + !if ($newArg > "") + !return $newArg + !endif + !$tags = %substr($tags, $brPos+1) + !$brPos = %strpos($tags, "+") + !endwhile + !if (%strlen($tags) > 0) + !$newArg = %get_variable_value("$" + $tags + $varPostfix) + !if ($newArg > "") + !return $newArg + !endif + !$newArg = %get_variable_value("$" + $elementType + $varPostfix) + !if ($newArg > "") + !return $newArg + !endif + !endif + !return $arg +!endfunction + +' if $value is empty try to load it via variable, optional can it store the calculated value +!function $restoreEmpty($elementType, $property, $value, $store) + !$var = "$" + $elementType + "Restore" + $property + !if ($value == "") + !$value = %get_variable_value($var) + !elseif ($store) + %set_variable_value($var, $value) + !endif + !return $value +!endfunction + +' clear the restore property +!function $clearRestore($elementType, $property) + !$var = "$" + $elementType + "Restore" + $property + %set_variable_value($var, "") + !return "" +!endfunction + +!function $elementTagSkinparams($element, $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $borderStyle, $borderThickness) + !$elementSkin = "skinparam " + $element + "<<" + $tagStereo + ">> {" + $bl() + !if ($fontColor != "") + !if (%strpos($tagStereo, "boundary") < 0) + !$elementSkin = $elementSkin + " StereotypeFontColor " + $fontColor + $bl() + !endif + !$elementSkin = $elementSkin + " FontColor " + $fontColor + $bl() + !endif + !if ($bgColor != "") + !$elementSkin = $elementSkin + " BackgroundColor " + $bgColor + $bl() + !endif + !if ($borderColor != "") + !$elementSkin = $elementSkin + " BorderColor " + $borderColor+ $bl() + !endif + !if ($shadowing == "true") + !$elementSkin = $elementSkin + " Shadowing<<" + $tagStereo + ">> " + "true" + $bl() + !endif + !if ($shadowing == "false") + !$elementSkin = $elementSkin + " Shadowing<<" + $tagStereo + ">> " + "false" + $bl() + !endif + ' only rectangle supports shape(d corners), define both skinparam that overlays are working + !if ($shape != "" && $element == "rectangle") + !if ($shape == $SHARP_CORNER) + !$elementSkin = $elementSkin + " RoundCorner " + "0" + $bl() + !$elementSkin = $elementSkin + " DiagonalCorner " + "0" + $bl() + !elseif ($shape == $ROUNDED_BOX) + !$elementSkin = $elementSkin + " RoundCorner " + $ROUNDED_BOX_SIZE+ $bl() + !$elementSkin = $elementSkin + " DiagonalCorner " + "0" + $bl() + !elseif ($shape == $EIGHT_SIDED) + !$elementSkin = $elementSkin + " RoundCorner " + "0" + $bl() + !$elementSkin = $elementSkin + " DiagonalCorner " + $EIGHT_SIDED_SIZE+ $bl() + !endif + !endif + !if ($borderStyle != "") + !$elementSkin = $elementSkin + " BorderStyle " + $borderStyle + $bl() + !endif + !if ($borderThickness != "") + !$elementSkin = $elementSkin + " BorderThickness " + $borderThickness + $bl() + !endif + !$elementSkin = $elementSkin + "}" + $bl() + !return $elementSkin +!endfunction + +!unquoted procedure $defineSkinparams($tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $borderStyle, $borderThickness) + ' only rectangle supports shape(d corners) + !$tagSkin = $elementTagSkinparams("rectangle", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("database", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("queue", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + ' plantuml.jar bug - actor have to be after person + !$tagSkin = $tagSkin + $elementTagSkinparams("person", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + ' actor has style awesome, therefore $fontColor is ignored and text uses $bgColor too + !$tagSkin = $tagSkin + $elementTagSkinparams("actor", $tagStereo, $bgColor, $bgColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + ' sequence requires participant + !$tagSkin = $tagSkin + $elementTagSkinparams("participant", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("sequencebox", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !if (%strpos($tagStereo, "boundary") >= 0 && $bgColor != "") + !$tagSkin = $tagSkin + "skinparam package<<" + $tagStereo + ">>StereotypeFontColor " + $bgColor + $bl() + !$tagSkin = $tagSkin + "skinparam rectangle<<" + $tagStereo + ">>StereotypeFontColor " + $bgColor + $bl() + !endif + !if %variable_exists("ENABLE_ALL_PLANT_ELEMENTS") + !$tagSkin = $tagSkin + $elementTagSkinparams("agent", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("artifact", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("card", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("cloud", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("collections", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("file", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("folder", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("frame", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("hexagon", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("package", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("stack", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("storage", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("usecase", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + ' elements without background: font uses $bgColor + !$tagSkin = $tagSkin + $elementTagSkinparams("boundary", $tagStereo, $bgColor, $bgColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("circle", $tagStereo, $bgColor, $bgColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("control", $tagStereo, $bgColor, $bgColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("entity", $tagStereo, $bgColor, $bgColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + !$tagSkin = $tagSkin + $elementTagSkinparams("interface", $tagStereo, $bgColor, $bgColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) + ' label uses wrong font color? (should be $bgColor too) + !$tagSkin = $tagSkin + $elementTagSkinparams("label", $tagStereo, $bgColor, $bgColor, $borderColor, $shadowing, "", $borderStyle, $borderThickness) +' label colors cannot be set via skinparam use additional style + !$tagSkin = $tagSkin + "" + $bl() + !endif +$tagSkin +!endprocedure + +' arrow colors cannot start with # (legend background has to start with #) +!function $colorWithoutHash($c) + !if (%substr($c, 0, 1) == "#") + !$c = %substr($c,1) + !endif + !return $c +!endfunction + +!unquoted procedure $defineRelSkinparams($tagStereo, $textColor, $lineColor, $lineStyle, $lineThickness) + !$elementSkin = "skinparam arrow<<" + $tagStereo + ">> {" + $bl() + !if ($lineColor != "") || ($textColor != "") || ($lineStyle != "") + !$elementSkin = $elementSkin + " Color " + !if ($lineColor != "") + !$elementSkin = $elementSkin + $colorWithoutHash($lineColor) + !endif + !if ($textColor != "") + !$elementSkin = $elementSkin + ";text:" + $colorWithoutHash($textColor) + !endif + !if ($lineStyle != "") + !$elementSkin = $elementSkin + ";line." + $lineStyle + !endif + !$elementSkin = $elementSkin + $bl() + !endif + !if ($lineThickness != "") + !$elementSkin = $elementSkin + " thickness " + $lineThickness + $bl() + !endif + !$elementSkin = $elementSkin + "}" + $bl() +$elementSkin +!endprocedure + +' %is_dark() requires PlantUML version >= 1.2021.6 +!if (%function_exists("%is_dark")) + !$PlantUMLSupportsDynamicLegendColor = %true() +!else + !$PlantUMLSupportsDynamicLegendColor = %false() + !log "dynamic undefined legend colors" requires PlantUML version >= 1.2021.6, therefore only static assigned colors are used +!endif + +!unquoted function $contrastLegend($color) + !if (%is_dark($color)) + !$value = $LEGEND_LIGHT_COLOR + !else + !$value = $LEGEND_DARK_COLOR + !endif + !return $value +!endfunction + +!unquoted function $flatLegend($color) + !if (%is_dark($color)) + !$value = $LEGEND_DARK_COLOR + !else + !$value = $LEGEND_LIGHT_COLOR + !endif + !return $value +!endfunction + +' legend background has to start with # +!function $colorWithHash($c) + !if (%substr($c, 0, 1) != "#") + !$c = "#" + $c + !endif + !return $c +!endfunction + +!function $addMaskFlag($mask, $attr) + !if ($attr == "") + !$mask = $mask + "0" + !else + !$mask = $mask + "1" + !endif + !return $mask +!endfunction + +!function $orFlags($flag1, $flag2) + !if ($flag1 == "0" && $flag2 == "0") + !return "0" + !endif + !return "1" +!endfunction + +!function $tagLegendMask($bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $borderStyle, $borderThickness) + !$mask = "" + !$mask = $addMaskFlag($mask, $bgColor) + !$mask = $addMaskFlag($mask, $fontColor) + !$mask = $addMaskFlag($mask, $borderColor) + !$mask = $addMaskFlag($mask, $shadowing) + !$mask = $addMaskFlag($mask, $shape) + !$mask = $addMaskFlag($mask, $sprite) + !$mask = $addMaskFlag($mask, $borderStyle) + !$mask = $addMaskFlag($mask, $borderThickness) + !return $mask +!endfunction + +!function $resetMask() + !return "00000000" +!endfunction + +!function $combineMasks($mask1, $mask2) + !$mask = "" + !$mask = $mask + $orFlags(%substr($mask1, 0, 1), %substr($mask2, 0, 1)) + !$mask = $mask + $orFlags(%substr($mask1, 1, 1), %substr($mask2, 1, 1)) + !$mask = $mask + $orFlags(%substr($mask1, 2, 1), %substr($mask2, 2, 1)) + !$mask = $mask + $orFlags(%substr($mask1, 3, 1), %substr($mask2, 3, 1)) + !$mask = $mask + $orFlags(%substr($mask1, 4, 1), %substr($mask2, 4, 1)) + !$mask = $mask + $orFlags(%substr($mask1, 5, 1), %substr($mask2, 5, 1)) + !$mask = $mask + $orFlags(%substr($mask1, 6, 1), %substr($mask2, 6, 1)) + !$mask = $mask + $orFlags(%substr($mask1, 7, 1), %substr($mask2, 7, 1)) + !return $mask +!endfunction + +!function $combineMaskWithTag($mask1, $tag) + !$mask2 = %get_variable_value("$" + $tag+ "LegendMask") + !if ($mask2 == "") + ' !log combineMaskWithTag $mask1, $tag, ... only $mask1 + !return $mask1 + !endif + + ' !log combineMaskWithTag $mask1, $tag, $mask2 ... $combineMasks($mask1, $mask2) + !return $combineMasks($mask1, $mask2) +!endfunction + +' element symbols typically 4 times too big in legend +!function $smallVersionSprite($sprite, $imageScale = $LEGEND_IMAGE_SIZE_FACTOR) + ' ,scale= ... has to be first (...,color=black,scale=0.25... is invalid too) + !if (%strpos($sprite, "=") < 0) + !if (%substr($sprite, 0, 4) == "img:") + !$smallSprite = $sprite + "{scale=" + $imageScale + "}" + !else + !$smallSprite = $sprite + ",scale=" + $imageScale + !endif + !else + !$smallSprite = $sprite + !endif + !return $smallSprite +!endfunction + +' format sprite that it can be used in diagram +!function $getSprite($sprite) + ' if it starts with & it's a OpenIconic, details see https://useiconic.com/open/ + ' if it starts with img: it's an image, details see https://plantuml.com/creole + !if (%substr($sprite, 0, 1) != "&" && %substr($sprite, 0, 4) != "img:") + !$formatted = "<$" + $sprite + ">" + !else + !$formatted = "<" + $sprite + ">" + !endif + !return $formatted +!endfunction + +!function $setTagLegendVariables($tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $legendText, $legendSprite, $borderStyle, $borderThickness) + !$bg = $bgColor + !$fo = $fontColor + !$bo = $borderColor + + !if ($fo == "") + !if ($bg != "") +!if ($PlantUMLSupportsDynamicLegendColor) + !$fo = $contrastLegend($bg) +!else + !$fo = $LEGEND_DARK_COLOR +!endif + !else + !if ($bo == "") + !$fo = $LEGEND_DARK_COLOR + !$bg = $LEGEND_LIGHT_COLOR + !else +!if ($PlantUMLSupportsDynamicLegendColor) + !$fo = $flatLegend($bo) + !$bg = $contrastLegend($bo) +!else + !$fo = $LEGEND_DARK_COLOR + !$bg = $LEGEND_LIGHT_COLOR +!endif + !endif + !endif + !else + !if ($bg == "") +!if ($PlantUMLSupportsDynamicLegendColor) + !$bg = $contrastLegend($fo) +!else + !$bg = $LEGEND_LIGHT_COLOR +!endif + !endif + !endif + + !if ($bo == "") + !$bo = $bg + !endif + + !$tagEntry = "|" + !$tagDetails = "(" + ' If the element background is transparent (boundaries), force a visible swatch in legend. + !if ($bgColor == "#00000000" || %lower($bgColor) == "transparent") + !$bg = "#FFFFFF" + !endif + !$tagEntry = $tagEntry + "<" + $colorWithHash($bg) +">" + ' Do not render any special glyph marker here; environments without the glyph/font show squares. + !$tagEntry = $tagEntry + "" + !if ($legendSprite != "") + !$tagEntry = $tagEntry + $getSprite($legendSprite) + " " + !endif + + !$isBoundary = 0 + !if ($legendText == "") + !if (%strpos($tagStereo, "boundary") >= 0) + !if ($tagStereo == "boundary") + !$isBoundary = 1 + !$tagEntry = $LEGEND_BOUNDARY_PRE_PART + $tagEntry + $LEGEND_BOUNDARY_POST_PART + " " + !else + ' if contains/ends with _boundary remove _boundary and add "boundary (dashed)" + !$pos = %strpos($tagStereo, "_boundary") + !if ($pos > 0) + !$isBoundary = 1 + !$tagEntry = $tagEntry + " " + $LEGEND_BOUNDARY_PRE_PART + %substr($tagStereo, 0 ,$pos) + $LEGEND_BOUNDARY_POST_PART + " " + !endif + !endif + !endif + !if ($isBoundary == 0) + !$tagEntry = $tagEntry + " " + $tagStereo + " " + !endif + + !if ($isBoundary == 1 && ($bgColor == "#00000000" || %lower($bgColor) == "transparent")) + !$tagDetails = $tagDetails + $LEGEND_BOUNDARY_TRANSPARENT_INCL_COMA + !endif + !if ($shadowing == "true") + !$tagDetails = $tagDetails + $LEGEND_SHADOW_TEXT + ", " + !endif + !if ($shadowing == "false") + !$tagDetails = $tagDetails + $LEGEND_NO_SHADOW_TEXT + ", " + !endif + !if ($shape == $SHARP_CORNER && $shape != $DEFAULT_SHAPE) + !$tagDetails = $tagDetails + $LEGEND_SHARP_CORNER + ", " + !endif + !if ($shape == $ROUNDED_BOX && $shape != $DEFAULT_SHAPE) + !$tagDetails = $tagDetails + $LEGEND_ROUNDED_BOX + ", " + !endif + !if ($shape == $EIGHT_SIDED && $shape != $DEFAULT_SHAPE) + !$tagDetails = $tagDetails + $LEGEND_EIGHT_SIDED + ", " + !endif + !if ($fontColor == "" && $bgColor == "") + !$tagDetails = $tagDetails + $LEGEND_NO_FONT_BG_TEXT + ", " + !else + !if ($fontColor == "") + !$tagDetails = $tagDetails + $LEGEND_NO_FONT_TEXT + ", " + !endif + !if ($bgColor == "") + !$tagDetails = $tagDetails + $LEGEND_NO_BG_TEXT + ", " + !endif + !endif + !if ($borderStyle != "") + !if ($borderStyle == $DOTTED_LINE) + !$tagDetails = $tagDetails + $LEGEND_DOTTED_LINE + ", " + !elseif ($borderStyle == $DASHED_LINE) + !if ($isBoundary == 1) + !$tagDetails = $tagDetails + $LEGEND_BOUNDARY_DASHED_INCL_COMA + !else + !$tagDetails = $tagDetails + $LEGEND_DASHED_LINE + ", " + !endif + !elseif ($borderStyle == $BOLD_LINE) + !$tagDetails = $tagDetails + $LEGEND_BOLD_LINE + ", " + !elseif ($borderStyle == $SOLID_LINE) + !$tagDetails = $tagDetails + $LEGEND_SOLID_LINE + ", " + !else + !$tagDetails = $tagDetails + $borderStyle + ", " + !endif + !endif + !if ($borderThickness != "") + !$tagDetails = $tagDetails + $LEGEND_THICKNESS + " " + $borderThickness + ", " + !endif + !if ($tagDetails=="(" || $tagDetails=="(, ") + !$tagDetails = "" + !else + !$tagDetails = %substr($tagDetails, 0, %strlen($tagDetails)-2) + !$tagDetails = $tagDetails + ")" + !endif + !else + !$brPos = %strpos($legendText, "\n") + !if ($brPos > 0) + !$tagEntry = $tagEntry + %substr($legendText, 0, $brPos) + " " + !$details = %substr($legendText, $brPos + 2) + !if ($details=="") + !$tagDetails = "" + !else + !$tagDetails = $tagDetails + $details + ")" + !endif + !else + !$tagEntry = $tagEntry + " " + $legendText + " " + !$tagDetails = "" + !endif + !endif + + !$tagDetails = $tagDetails + " " + !$tagDetails = $tagDetails + "|" +%set_variable_value("$" + $tagStereo + "LegendEntry", $tagEntry) +%set_variable_value("$" + $tagStereo + "LegendDetails", $tagDetails) + !return $tagEntry +!endfunction + +!function $setTagRelLegendVariables($tagStereo, $textColor, $lineColor, $lineStyle, $legendText, $legendSprite, $lineThickness) + !$tc = $textColor + !$lc = $lineColor + + !if ($tc == "") + !if ($PlantUMLSupportsDynamicLegendColor) + !$tc = $flatLegend($ARROW_FONT_COLOR) + !else + !$tc = $LEGEND_DARK_COLOR + !endif + !endif + !if ($lc == "") + !if ($PlantUMLSupportsDynamicLegendColor) + !$lc = $flatLegend($ARROW_COLOR) + !else + !$lc = $LEGEND_DARK_COLOR + !endif + !endif + + !$tagEntry = "|" + !$tagDetails = "(" + ' ..white line + !$tagEntry = $tagEntry + " " + !$tagEntry = $tagEntry + "" + !if ($legendSprite != "") + !$tagEntry = $tagEntry + $getSprite($legendSprite) + " " + !endif + !if ($legendText == "") + !$tagEntry = $tagEntry + " " + $tagStereo + " " + !if ($textColor == "") + !$tagDetails = $tagDetails + $LEGEND_NO_FONT_TEXT + ", " + !endif + !if ($lineColor == "") + !$tagDetails = $tagDetails + $LEGEND_NO_LINE_TEXT + ", " + !endif + !if ($lineStyle != "") + !if ($lineStyle == $DOTTED_LINE) + !$tagDetails = $tagDetails + $LEGEND_DOTTED_LINE + ", " + !elseif ($lineStyle == $DASHED_LINE) + !$tagDetails = $tagDetails + $LEGEND_DASHED_LINE + ", " + !elseif ($lineStyle == $BOLD_LINE) + !$tagDetails = $tagDetails + $LEGEND_BOLD_LINE + ", " + !else + !$tagDetails = $tagDetails + $lineStyle + ", " + !endif + !endif + !if ($lineThickness != "") + !$tagDetails = $tagDetails + $LEGEND_THICKNESS + " " + $lineThickness + ", " + !endif + !if ($tagDetails=="(") + !$tagDetails = "" + !else + !$tagDetails = %substr($tagDetails, 0, %strlen($tagDetails)-2) + !$tagDetails = $tagDetails + ")" + !endif + !else + !$brPos = %strpos($legendText, "\n") + !if ($brPos > 0) + !$tagEntry = $tagEntry + " " + %substr($legendText, 0, $brPos) + " " + !$details = %substr($legendText, $brPos + 2) + !if ($details=="") + !$tagDetails = "" + !else + !$tagDetails = $tagDetails + $details + ")" + !endif + !else + !$tagEntry = $tagEntry + " " + $legendText + " " + !$tagDetails = "" + !endif + !endif + + !$tagDetails = $tagDetails + " " + !$tagDetails = $tagDetails + "|" +%set_variable_value("$" + $tagStereo + "_LineLegendEntry", $tagEntry) +%set_variable_value("$" + $tagStereo + "_LineLegendDetails", $tagDetails) + !return $tagEntry +!endfunction + +!unquoted procedure $addTagToLegend($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $legendText="", $legendSprite="", $borderStyle="", $borderThickness="") +'' if a combined element tag is defined (e.g. "v1.0&v1.1") then it is typically a merged color, +'' like a new $fontColor="#fdae61" therefore it should be added to the legend +'' and the & combined tags will be not removed +' !if (%strpos($tagStereo, "&") < 0) + !$dummyAlreadyVariables = $setTagLegendVariables($tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $legendText, $legendSprite, $borderStyle, $borderThickness) + !$tagCustomLegend = $tagCustomLegend + $tagStereo + "\n" + !$tagMask = $tagLegendMask( $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $borderStyle, $borderThickness) +%set_variable_value("$" + $tagStereo + "LegendMask", $tagMask) +' !endif +!endprocedure + +!unquoted procedure $addRelTagToLegend($tagStereo, $textColor="", $lineColor="", $lineStyle="", $legendText="", $legendSprite="", $lineThickness="") +'' Arrows have a bug with stereotype/skinparams and cannot combine text colors of one stereotype +'' and the line color of another stereotype. Therefore the text color of one tag and the line color +'' of another tag have to be combined via a "workaround" tag ("v1.0&v1.1"). +'' This workaround tag could be theoretically removed in the legend but after that there would +'' be an inconsistency between the element tags and the rel tags and therefore +'' & combined workaround tags are not removed too (and in unlikely cases the color itself could be changed) +' !if (%strpos($tagStereo, "&") < 0) + !$dummyAlreadyVariables = $setTagRelLegendVariables($tagStereo, $textColor, $lineColor, $lineStyle, $legendText, $legendSprite, $lineThickness) + !$tagCustomLegend = $tagCustomLegend + $tagStereo + "_Line\n" +' !endif +!endprocedure + +!procedure $showActiveLegendEntries($allDefined) + !$brPos = %strpos($allDefined, "\n") + !while ($brPos >= 0) + !$tagStereo = %substr($allDefined, 0, $brPos) + !$allDefined = %substr($allDefined, $brPos+2) + !$brPos = %strpos($allDefined, "\n") + !if (%variable_exists("$" + $tagStereo + "Legend")) + ' is part of legendDetails + !$part1 = %get_variable_value("$" + $tagStereo + "LegendEntry") + !$partSize = "" + !$part2 = %get_variable_value("$" + $tagStereo + "LegendDetails") + !$line = $part1 + $partSize + $part2 +$line + !endif + !endwhile + !if (%strlen($allDefined) > 0) + !$tagStereo = $allDefined + !if (%variable_exists("$" + $tagStereo + "Legend")) + ' is part of legendDetails + !$part1 = %get_variable_value("$" + $tagStereo + "LegendEntry") + !$partSize = "" + !$part2 = %get_variable_value("$" + $tagStereo + "LegendDetails") + !$line = $part1 + $partSize + $part2 +$line + !endif + !endif +!endprocedure + +' normal rectangle +!function SharpCornerShape() +!return $SHARP_CORNER +!endfunction + +!function RoundedBoxShape() +!return $ROUNDED_BOX +!endfunction + +!function EightSidedShape() +!return $EIGHT_SIDED +!endfunction + +!function DottedLine() +!return $DOTTED_LINE +!endfunction + +!function DashedLine() +!return $DASHED_LINE +!endfunction + +!function BoldLine() +!return $BOLD_LINE +!endfunction + +!function SolidLine() +!return $SOLID_LINE +!endfunction + +' used by new defined tags +!unquoted procedure AddElementTag($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $techn="", $legendText="", $legendSprite="", $borderStyle="", $borderThickness="") + +!if (NEW_C4_STYLE == 1) + !$swap=$bgColor + !$bgColor=$fontColor + !$fontColor=$swap +!endif + +$defineSkinparams($tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $borderStyle, $borderThickness) + !if ($sprite!="") +%set_variable_value("$" + $tagStereo + "ElementTagSprite", $sprite) + !if ($legendSprite == "") + !$legendSprite = $smallVersionSprite($sprite) + !endif + !endif + !if ($techn != "") +%set_variable_value("$" + $tagStereo + "ElementTagTechn", $techn) + !endif +$addTagToLegend($tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure + +!unquoted procedure $addElementTagInclReuse($elementName, $tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $techn="", $legendText="", $legendSprite="", $borderStyle="", $borderThickness="") + +'stored tags are already swapped (swap before comparing) +!if (NEW_C4_STYLE == 1) + !$swap=$bgColor + !$bgColor=$fontColor + !$fontColor=$swap +!endif + + !$bgColor=$restoreEmpty($elementName, "bgColor", $bgColor, %false()) + !$fontColor=$restoreEmpty($elementName, "fontColor", $fontColor, %false()) + !$borderColor=$restoreEmpty($elementName, "borderColor", $borderColor, %false()) + !$shadowing=$restoreEmpty($elementName, "shadowing", $shadowing, %false()) + !$shape=$restoreEmpty($elementName, "shape", $shape, %false()) + !$sprite=$restoreEmpty($elementName, "sprite", $sprite, %false()) + !$techn=$restoreEmpty($elementName, "techn", $techn, %false()) + ' new style should has its own legend text + ' !$legendText=$restoreEmpty($elementName, "legendText", $legendText, %false()) + !$legendSprite=$restoreEmpty($elementName, "legendSprite", $legendSprite, %false()) + !$borderStyle=$restoreEmpty($elementName, "borderStyle", $borderStyle, %false()) + !$borderThickness=$restoreEmpty($elementName, "borderThickness", $borderThickness, %false()) + +' swap back +!if (NEW_C4_STYLE == 1) + !$swap=$bgColor + !$bgColor=$fontColor + !$fontColor=$swap +!endif + + AddElementTag($tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $techn, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure + +' used by new defined rel tags +!unquoted procedure AddRelTag($tagStereo, $textColor="", $lineColor="", $lineStyle="", $sprite="", $techn="", $legendText="", $legendSprite="", $lineThickness="") +$defineRelSkinparams($tagStereo, $textColor, $lineColor, $lineStyle, $lineThickness) + !if ($sprite != "") +%set_variable_value("$" + $tagStereo + "RelTagSprite", $sprite) + !if ($legendSprite == "") + ' relation symbols typically 1:1 no additional scale required + !$legendSprite = $sprite + !endif + !endif + !if ($techn != "") +%set_variable_value("$" + $tagStereo + "RelTagTechn", $techn) + !endif +$addRelTagToLegend($tagStereo, $textColor, $lineColor, $lineStyle, $legendText, $legendSprite, $lineThickness) +!endprocedure + +' update the style of existing elements like person, ... +!unquoted procedure UpdateElementStyle($elementName, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $techn="", $legendText="", $legendSprite="", $borderStyle="", $borderThickness="") + +!if (NEW_C4_STYLE == 1) + !$swap=$bgColor + !$bgColor=$fontColor + !$fontColor=$swap +!endif + +!$bgColor=$restoreEmpty($elementName, "bgColor", $bgColor, %true()) +!$fontColor=$restoreEmpty($elementName, "fontColor", $fontColor, %true()) +!$borderColor=$restoreEmpty($elementName, "borderColor", $borderColor, %true()) +!$shadowing=$restoreEmpty($elementName, "shadowing", $shadowing, %true()) +!$shape=$restoreEmpty($elementName, "shape", $shape, %true()) +!$sprite=$restoreEmpty($elementName, "sprite", $sprite, %true()) +!$techn=$restoreEmpty($elementName, "techn", $techn, %true()) +!$legendText=$restoreEmpty($elementName, "legendText", $legendText, %true()) +!$legendSprite=$restoreEmpty($elementName, "legendSprite", $legendSprite, %true()) +!$borderStyle=$restoreEmpty($elementName, "borderStyle", $borderStyle, %true()) +!$borderThickness=$restoreEmpty($elementName, "borderThickness", $borderThickness, %true()) + +$defineSkinparams($elementName, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $borderStyle, $borderThickness) + !if ($sprite != "") +%set_variable_value("$" + $elementName + "ElementTagSprite", $sprite) + !if ($legendSprite == "") + !$legendSprite = $smallVersionSprite($sprite) + !endif + !endif + !if ($techn != "") +%set_variable_value("$" + $elementName + "ElementTagTechn", $techn) + !endif + !$dummyAlreadyVariables = $setTagLegendVariables($elementName, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $legendText, $legendSprite, $borderStyle, $borderThickness) + ' default tags sets at least bgColor and fontColor + !$tagMask = $tagLegendMask("CHANGED", "CHANGED", $borderColor, $shadowing, $shape, $sprite, $borderStyle, $borderThickness) +%set_variable_value("$" + $elementName + "LegendMask", $tagMask) +!endprocedure + +/' @deprecated in favor of UpdateElementStyle '/ +!unquoted procedure UpdateSkinparamsAndLegendEntry($elementName, $bgColor="", $fontColor="", $borderColor="", $shadowing="") +UpdateElementStyle($elementName, $bgColor, $fontColor, $borderColor, $shadowing) +!endprocedure + +' update the style of default relation, it has to set both properties (combined statement not working) +!unquoted procedure UpdateRelStyle($textColor, $lineColor) + !$elementSkin = "skinparam arrow {" + $bl() + !$elementSkin = $elementSkin + " Color " + $lineColor + $bl() + !$elementSkin = $elementSkin + " FontColor " + $textColor + $bl() + !$elementSkin = $elementSkin + "}" + $bl() +$elementSkin +!endprocedure + +!unquoted procedure UpdateLegendTitle($newTitle) + !$LEGEND_TITLE_TEXT = $newTitle +!endprocedure + +' tags/stereotypes have to be delimited with \n +!unquoted procedure SetDefaultLegendEntries($tagStereoEntries) + !$tagDefaultLegend = $tagStereoEntries +!endprocedure + +' Links +' ################################## + +!function $getLink($link) + !if ($link != "") + !return "[[" + $link + "]]" + !else + !return "" + !endif +!endfunction + +' Line breaks +' ################################## + +!unquoted function $breakText($text, $usedNewLine, $widthStr="-1") +!$width = %intval($widthStr) +!$multiLine = "" +!if (%strpos($text, "\n") >= 0) + !while (%strpos($text, "\n") >= 0) + !$brPos = %strpos($text, "\n") + !if ($brPos > 0) + !$multiLine = $multiLine + %substr($text, 0, $brPos) + $usedNewLine + !else + ' non breaking change that newLine breaks with formats can be used with \n\n + !$multiLine = $multiLine + "" + $usedNewLine + !endif + !$text = %substr($text, $brPos+2) + !if (%strlen($text) == 0) + !$text = "" + !endif + !endwhile +!else + !while ($width>0 && %strlen($text) > $width) + !$brPos = $width + !while ($brPos > 0 && %substr($text, $brPos, 1) != ' ') + !$brPos = $brPos - 1 + !endwhile + + !if ($brPos < 1) + !$brPos = %strpos($text, " ") + !else + !endif + + !if ($brPos > 0) + !$multiLine = $multiLine + %substr($text, 0, $brPos) + $usedNewLine + !$text = %substr($text, $brPos + 1) + !else + !$multiLine = $multiLine+ $text + !$text = "" + !endif + !endwhile +!endif +!if (%strlen($text) > 0) + !$multiLine = $multiLine + $text +!endif +!return $multiLine +!endfunction + +!unquoted function $breakLabel($text) +!$usedNewLine = "\n== " +!$multiLine = $breakText($text, $usedNewLine) +!return $multiLine +!endfunction + +!unquoted function $breakDescr($text, $widthStr) + !$usedNewLine = "\n" + !return $breakText($text, $usedNewLine, $widthStr) +!endfunction + +' $breakTechn() supports //...//; $breakNode() in C4_Deployment supports no //....// +!unquoted function $breakTechn($text, $widthStr) + !$usedNewLine = '//\n//' + !return $breakText($text, $usedNewLine, $widthStr) +!endfunction + +' Element base layout +' ################################## + +!function $getElementBase($label, $techn, $descr, $sprite) + !$element = "" + !if ($sprite != "") + !$element = $element + $getSprite($sprite) + !if ($label != "") + !$element = $element + '\n' + !endif + !endif + !if ($label != "") + !$element = $element + '== ' + $breakLabel($label) + !else + !$element = $element + '.' + !endif + !if ($techn != "") + !$element = $element + '\n//[' + $breakTechn($techn, '-1') + ']//' + !endif + !if ($descr != "") + !$element = $element + '\n\n' + $descr + !endif + !return $element +!endfunction + +!function $getElementLine($umlShape, $elementType, $alias, $label, $techn, $descr, $sprite, $tags, $link) + !$sprite=$toElementArg($sprite, $tags, "ElementTagSprite", $elementType) + !$techn=$toElementArg($techn, $tags, "ElementTagTechn", $elementType) + !$techn=$updateTechWithElementType($techn, $elementType) + !$baseProp = $getElementBase($label, $techn, $descr, $sprite) + $getProps() + !$stereo = $toStereos($elementType,$tags) + !$calcLink = $getLink($link) + + !$line = $umlShape + " " + %chr(34) + $baseProp + %chr(34) +" " + $stereo + " as " + $alias + " " + $calcLink + !return $line +!endfunction + +' Element properties +' ################################## + +' collect all defined properties as table rows +!global $propTable = "" +!global $propTableCaption = "" +!global $propColCaption = "=" + +!global $isFirstProp = 1 +!global $firstPropCol = 1 +!global $lastPropCol = 1 + +!function $fillMissing($col, $colNext) + !if ($col == "" && $colNext != "") + !return " " + !endif + !return $col +!endfunction + +!function $updatePropColumns($colIdx) + !if ($isFirstProp == 1 && $colIdx > $firstPropCol) + !$firstPropCol = $colIdx + !endif + !if ($isFirstProp == 0 && $colIdx > $lastPropCol) + !$lastPropCol = $colIdx + !endif + !return "" +!endfunction + +' add missing header columns, if a following row has more columns +' (fixed in PlantUML v1.2025.1beta9; only required in older versions) +!function $fixHeaderColumns() + ' the number of displayed columns considers only the first row + ' if another row has more columns the first has to be filled with missing columns + !if ($lastPropCol > $firstPropCol) + !$delta = $lastPropCol - $firstPropCol + !$delta = $delta * 2 + !$fix = %substr(" | | | |", 0, $delta) + + ' basically the line break \n should be the split + ' but \n is not encoded (anymore?) therefore split only via + ' \ and remove the last obsolete \ (changed order with add + ' \ at the beginning is not working). + ' "\n" would split \ and n ==> n would be an unwanted line break + !$lines = %splitstr($propTable, "\") + ' !$lines = %splitstr_regex($propTable, "(?=[\x000A])") + !$first = 1 + !$newTab = "" + !foreach $item in $lines + !if ($first == 1) + !$item = $item + $fix + !$first = 0 + !endif + !$newTab = $newTab + $item + "\" + !endfor + + !$fixLen = %strlen($newTab) - 1 + !$newTab = %substr($newTab, 0, $fixLen) + + !$propTable = $newTab + !endif + + !$isFirstProp = 1 + !$firstPropCol = 1 + !$lastPropCol = 1 + + !return "" +!endfunction + +!unquoted function SetPropertyHeader($col1Name, $col2Name = "", $col3Name = "", $col4Name = "") + !$col3Name = $fillMissing($col3Name, $col4Name) + !$col2Name = $fillMissing($col2Name, $col3Name) + !$col1Name = $fillMissing($col1Name, $col2Name) + + !$propColCaption = "" + !$propTableCaption = "|= " + $col1Name + " |" + !if ($col2Name != "") + !$propTableCaption = $propTableCaption + "= " + $col2Name + " |" + $updatePropColumns(2) + !endif + !if ($col3Name != "") + !$propTableCaption = $propTableCaption + "= " + $col3Name + " |" + $updatePropColumns(3) + !endif + !if ($col4Name != "") + !$propTableCaption = $propTableCaption + "= " + $col4Name + " |" + $updatePropColumns(4) + !endif + + !$isFirstProp = 0 + !return "" +!endfunction + +!unquoted function WithoutPropertyHeader() + !$propTableCaption = "" + !$propColCaption = "=" + + !$isFirstProp = 1 + !$firstPropCol = 1 + !$lastPropCol = 1 + + !return "" +!endfunction + +!unquoted function AddProperty($col1, $col2 = "", $col3 = "", $col4 = "") + !$col3 = $fillMissing($col3, $col4) + !$col2 = $fillMissing($col2, $col3) + !$col1 = $fillMissing($col1, $col2) + + !if ($propTable == "") + !if ($propTableCaption != "") + !$propTable = $propTableCaption + "\n" + !endif + !else + !$propTable = $propTable + "\n" + !endif + + !$propTable = $propTable + "| " + $col1 + " |" + !if ($col2 != "") + !$propTable = $propTable + $propColCaption + " " + $col2 + " |" + $updatePropColumns(2) + !endif + !if ($col3 != "") + !$propTable = $propTable + " " + $col3 + " |" + $updatePropColumns(3) + !endif + !if ($col4 != "") + !$propTable = $propTable + " " + $col4 + " |" + $updatePropColumns(4) + !endif + + !$isFirstProp = 0 + !return "" +!endfunction + +!unquoted function $getProps($alignedNL = "\n") + $fixHeaderColumns() + + !if ($propTable != "") + !$retTable = $alignedNL + $propTable + !$propTable = "" + !return $retTable + !endif + !return "" +!endfunction + +!unquoted function $getProps_L() + !return $getProps("\l") +!endfunction + +!unquoted function $getProps_R() + !return $getProps("\r") +!endfunction + +SetPropertyHeader("Property","Value") + +' Layout +' ################################## + +!function $getLegendDetailsSize($detailsFormat) + !if $detailsFormat == $LEGEND_DETAILS_NONE + !$size = 0 + !elseif $detailsFormat == $LEGEND_DETAILS_SMALL + !$size = $LEGEND_DETAILS_SMALL_SIZE + !else + !$size = $LEGEND_DETAILS_NORMAL_SIZE + !endif + !return $size +!endfunction + +!procedure $getHideStereotype($hideStereotype) +!if ($hideStereotype == "true") +hide stereotype +!endif +!endprocedure + +!procedure $getLegendTable($detailsFormat) +!global $LEGEND_DETAILS_SIZE = $getLegendDetailsSize($detailsFormat) +<$colorWithHash(transparent),$colorWithHash(transparent)>|**$LEGEND_TITLE_TEXT ** | +$showActiveLegendEntries($tagDefaultLegend) +$showActiveLegendEntries($tagCustomLegend) +!endprocedure + +!procedure $getLegendArea($areaAlias, $hideStereotype, $details) +$getHideStereotype($hideStereotype) +rectangle $areaAlias<> [ +$getLegendTable($details) +] +!endprocedure + +!procedure HIDE_STEREOTYPE() +hide stereotype +!endprocedure + +!unquoted procedure SET_SKETCH_STYLE($bgColor="_dont_change_", $fontColor="_dont_change_", $warningColor="_dont_change_", $fontName="_dont_change_", $footerWarning="_dont_change_", $footerText="_dont_change_") +!if $bgColor != "_dont_change_" + !global $SKETCH_BG_COLOR = $bgColor +!endif +!if $fontColor != "_dont_change_" + !global $SKETCH_FONT_COLOR = $fontColor +!endif +!if $warningColor != "_dont_change_" + !global $SKETCH_WARNING_COLOR = $warningColor +!endif +!if $fontName != "_dont_change_" + !global $SKETCH_FONT_NAME = $fontName +!endif +!if $footerWarning != "_dont_change_" + !global $SKETCH_FOOTER_WARNING = $footerWarning +!endif +!if $footerText != "_dont_change_" + !global $SKETCH_FOOTER_TEXT = $footerText +!endif +!endprocedure + +!procedure LAYOUT_AS_SKETCH() +!$counter=0 +!foreach $versionPart in %splitstr(%version(), ".") + !$counter=$counter+1 + + !if ($counter == 2) + !$year=$versionPart + !endif + + !if ($counter == 3) + !$minor=$versionPart + !endif +!endfor + +!if ($year < 2025) || ($year == 2025 && $minor == 0) + skinparam handwritten true +!else + !option handwritten true +!endif + +!if $SKETCH_BG_COLOR > "" + skinparam backgroundColor $SKETCH_BG_COLOR +!endif +!if $SKETCH_FONT_COLOR > "" + skinparam footer { + FontColor $SKETCH_FONT_COLOR + } + !if $ARROW_COLOR == "#666666" + !global $ARROW_COLOR = $SKETCH_FONT_COLOR + !global $ARROW_FONT_COLOR = $SKETCH_FONT_COLOR + skinparam arrow { + Color $ARROW_COLOR + FontColor $ARROW_FONT_COLOR + } + !endif + !if $BOUNDARY_COLOR == "#444444" + !global $BOUNDARY_COLOR = $SKETCH_FONT_COLOR + skinparam rectangle<> { + FontColor $BOUNDARY_COLOR + BorderColor $BOUNDARY_COLOR + } + !endif +!endif +!if $SKETCH_FONT_NAMES > "" + skinparam defaultFontName $SKETCH_FONT_NAME +!endif +!if $SKETCH_FOOTER_WARNING > "" || $SKETCH_FOOTER_TEXT > "" + !$line = "footer "+ $SKETCH_FOOTER_WARNING + " " + $SKETCH_FOOTER_TEXT + $line +!endif +!endprocedure + +!global $fix_direction=%false() + +!function $down($start,$end) +!if ($fix_direction) +!return $start+"RIGHT"+$end +!else +!return $start+"DOWN"+$end +!endif +!endfunction + +!function $up($start,$end) +!if ($fix_direction) +!return $start+"LEFT"+$end +!else +!return $start+"UP"+$end +!endif +!endfunction + +!function $left($start,$end) +!if ($fix_direction) +!return $start+"UP"+$end +!else +!return $start+"LEFT"+$end +!endif +!endfunction + +!function $right($start,$end) +!if ($fix_direction) +!return $start+"DOWN"+$end +!else +!return $start+"RIGHT"+$end +!endif +!endfunction + +!procedure LAYOUT_TOP_DOWN() +!global $fix_direction=%false() +top to bottom direction +!endprocedure + +!procedure LAYOUT_LEFT_RIGHT() +!global $fix_direction = %false() +left to right direction +!endprocedure + +!procedure LAYOUT_LANDSCAPE() +!global $fix_direction = %true() +left to right direction +!endprocedure + +' legend details can displayed as Normal(), Small(), None() +!function None() +!return $LEGEND_DETAILS_NONE +!endfunction + +!function Normal() +!return $LEGEND_DETAILS_NORMAL +!endfunction + +!function Small() +!return $LEGEND_DETAILS_SMALL +!endfunction + +' has to be last call in diagram +!unquoted procedure SHOW_LEGEND($hideStereotype="true", $details=Small()) +$getHideStereotype($hideStereotype) +legend right +$getLegendTable($details) +endlegend +!endprocedure + +/' @deprecated in favor of SHOW_LEGEND '/ +!unquoted procedure SHOW_DYNAMIC_LEGEND($hideStereotype="true") +SHOW_LEGEND($hideStereotype) +!endprocedure + +' legend is reserved and cannot be uses as alias of SHOW_FLOATING_LEGEND() therefore +' LEGEND() is introduced. It returns the default name of the floating alias "floating_legend_alias" +' and can be used in the Lay_Distance() calls +!function LEGEND() +!return "floating_legend_alias" +!endfunction + +' enables that legend can be located in drawing area of the diagram. It has to be last call in diagram followed by Lay_Distance() +!unquoted procedure SHOW_FLOATING_LEGEND($alias=LEGEND(), $hideStereotype="true", $details=Small()) +$getLegendArea($alias, $hideStereotype, $details) +!endprocedure + +'SHOW_ELEMENT_TYPE() shows the element type as part of technology text (and typically hides stereotype too). +!global $showElementTypeViaTech = %false() +!global $showPersonTypeViaTech = %false() +!unquoted procedure SHOW_ELEMENT_TYPE($hideStereotype="true", $hidePersonType="true") + !global $showElementTypeViaTech = %true() + $getHideStereotype($hideStereotype) + !if ($hidePersonType == "false" || $hidePersonType == %false()) + !global $showPersonTypeViaTech = %true() + !endif +!endprocedure + +!unquoted function $updateTechWithElementType($techn, $elementType) + !if $showElementTypeViaTech + !$legendText=$restoreEmpty($elementType, "legendText", "", %false()) + !if $techn>"" + !$techn=$legendText + ": " + $techn + !else + !$techn=$legendText + !endif + !endif + !return $techn +!endfunction + +' Boundaries +' ################################## + +!unquoted procedure UpdateBoundaryStyle($elementName="", $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $type="", $legendText="", $borderStyle="", $borderThickness="", $sprite="", $legendSprite="") + +!if (NEW_C4_STYLE == 1) + !$swap=$bgColor + !$bgColor=$fontColor + !$fontColor=$swap +!endif + + !if ($elementName != "") + !$elementBoundary = $elementName + '_boundary' + UpdateElementStyle($elementBoundary, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $type, $legendText, $legendSprite, $borderStyle, $borderThickness) + !else + UpdateElementStyle("boundary", $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $type, $legendText, $legendSprite, $borderStyle, $borderThickness) + ' simulate color inheritance + UpdateBoundaryStyle("enterprise", $bgColor, $fontColor, $borderColor, $shadowing, $shape, "Enterprise", "", $borderStyle, $borderThickness, $sprite, $legendSprite) + UpdateBoundaryStyle("system", $bgColor, $fontColor, $borderColor, $shadowing, $shape, "System", "", $borderStyle, $borderThickness, $sprite, $legendSprite) + UpdateBoundaryStyle("container", $bgColor, $fontColor, $borderColor, $shadowing, $shape, "Container", "", $borderStyle, $borderThickness, $sprite, $legendSprite) + !endif +!endprocedure + +!unquoted procedure AddBoundaryTag($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $type="", $legendText="", $borderStyle="", $borderThickness="", $sprite="", $legendSprite="") + !$tagBoundary = $tagStereo + '_boundary' + AddElementTag($tagBoundary, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $type, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure + +' add _boundary to all tags that short tag version can be used +!unquoted function $addBoundaryPostfix($tags) + !if (%strlen($tags) == 0) + !return '' + !endif + !$boundaryTags = '' + !$brPos = %strpos($tags, "+") + !while ($brPos >= 0) + !$tag = %substr($tags, 0, $brPos) + !$boundaryTags = $boundaryTags + $tag + '_boundary+' + !$tags = %substr($tags, $brPos+1) + !$brPos = %strpos($tags, "+") + !endwhile + !if (%strlen($tags) > 0) + !$boundaryTags = $boundaryTags + $tags + '_boundary' + !endif + !return $boundaryTags +!endfunction + +!function $getBoundary($label, $type, $descr, $sprite) + !$line = '== ' + !if ($sprite != "") + ' add sprite in label line that it is more compact + !$line = $line + $getSprite($smallVersionSprite($sprite, $BOUNDARY_IMAGE_SIZE_FACTOR)) + ' ' + !endif + !$line = $line + $breakLabel($label) + !if ($type != "") + !$line = $line + '\n[' + $type + ']' + !endif + !if ($descr != "") + !$line = $line + '\n\n' + $breakDescr($descr, $BOUNDARY_DESCR_MAX_CHAR_WIDTH) + !endif + !return $line +!endfunction + +!unquoted procedure Boundary($alias, $label, $type="", $tags="", $link="", $descr = "") +!$boundaryTags = $addBoundaryPostfix($tags) +' boundary $type reuses $techn definition of $boundaryTags +!$type=$toElementArg($type, $boundaryTags, "ElementTagTechn", "boundary") +!$sprite=$toElementArg("", $boundaryTags, "ElementTagSprite", "boundary") +rectangle "$getBoundary($label, $type, $descr, $sprite)" $toStereos("boundary", $boundaryTags) as $alias $getLink($link) +!endprocedure + +' Boundary Styling +UpdateBoundaryStyle("", $bgColor=$BOUNDARY_BG_COLOR, $fontColor=$BOUNDARY_COLOR, $borderColor=$BOUNDARY_COLOR, $borderStyle=DashedLine(), $legendText="$BOUNDARY_LEGEND_TEXT") + +' Index +' ################################## + +' Dynamic/Sequence diagram supports (automatically) numbered interactions: +' preferred function calls +' (Uppercase) LastIndex(): return the last used index (function which can be used as argument) +' (Uppercase) Index($offset=1): returns current index and calculates next index (function which can be used as argument) +' (Uppercase) SetIndex($new_index): returns new set index and calculates next index (function which can be used as argument) + +' old procedures calls +' (lowercase) increment($offset=1): increase current index (procedure which has no direct output) +' (lowercase) setIndex($new_index): set the new index (procedure which has no direct output) + +!$lastIndex = 0 +!$index = 1 + +!procedure increment($offset=1) + !$lastIndex = $index + !$index = $index + $offset +!endprocedure + +!procedure setIndex($new_index) + !$lastIndex = $index + !$index = $new_index +!endprocedure + +!function Index($offset=1) + !$lastIndex = $index + !$index = $lastIndex + $offset + !return $lastIndex +!endfunction + +!function LastIndex() + !return $lastIndex +!endfunction + +!function SetIndex($new_index, $offset=1) + !$lastIndex = $new_index + !$index = $new_index + $offset + !return $lastIndex +!endfunction + +!unquoted function $getPrefix($index) + !if ($index == "") + !$pre = Index() + ": " + !else + !$pre = $index + ": " + !endif + !return $pre +!endfunction + +' Relationship +' ################################## + +!function $getRel($direction, $alias1, $alias2, $label, $techn, $descr, $sprite, $tags, $link) + !$sprite = $toRelArg($sprite, $tags, "RelTagSprite") + !$techn = $toRelArg($techn, $tags, "RelTagTechn") + !$rel = $alias1 + ' ' + $direction + ' ' + $alias2 + !if ($tags != "") + !$rel = $rel + ' ' + $toStereos($tags) + !endif + !$rel = $rel + ' : ' + !if ($link != "") + !$rel = $rel + '**[[' + $link + ' ' + !endif + !if ($sprite != "") + !$rel = $rel + $getSprite($sprite) + !if ($label != "") + !$rel = $rel + ' ' + !endif + !endif + !if ($link != "") + !$usedNewLine = ']]**\n**[[' + $link + ' ' + ' if sprite and label is empty than the link url is shown (otherwise link cannot be activated at all) + !$rel = $rel + $breakText($label, $usedNewLine) + ']]**' + !else + !if ($label != "") + !$usedNewLine = '**\n**' + !$rel = $rel + '**' + $breakText($label, $usedNewLine) + '**' + !else + !$rel = $rel + '.' + !endif + !endif + !if ($techn != "") + ' line break is not deterministic, calculate it + !$rel = $rel + '\n//[' + $breakTechn($techn, $REL_TECHN_MAX_CHAR_WIDTH) + ']//' + !endif + !if ($descr != "") + ' line break is not deterministic, calculate it + !$rel = $rel + '\n\n' + $breakDescr($descr, $REL_DESCR_MAX_CHAR_WIDTH) + !endif + !$prop = $getProps() + !if ($prop != "") + ' reuse table + !$rel = $rel + $prop + !endif + !return $rel +!endfunction + +!unquoted procedure Rel_($alias1, $alias2, $label, $direction) +$getRel($direction, $alias1, $alias2, $label, "", "", "", "", "") +!endprocedure +!unquoted procedure Rel_($alias1, $alias2, $label, $techn, $direction) +$getRel($direction, $alias1, $alias2, $label, $techn, "", "", "", "") +!endprocedure + +!unquoted procedure Rel($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel("-->>", $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure BiRel($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel("<<-->>", $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure Rel_Back($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel("<<--", $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure Rel_Neighbor($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel("->>", $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure BiRel_Neighbor($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel("<<->>", $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure Rel_Back_Neighbor($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel("<<-", $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure Rel_D($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($down("-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure +!unquoted procedure Rel_Down($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($down("-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure BiRel_D($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($down("<<-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure +!unquoted procedure BiRel_Down($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($down("<<-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure Rel_U($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($up("-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure +!unquoted procedure Rel_Up($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($up("-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure BiRel_U($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($up("<<-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure +!unquoted procedure BiRel_Up($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($up("<<-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure Rel_L($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($left("-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure +!unquoted procedure Rel_Left($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($left("-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure BiRel_L($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($left("<<-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure +!unquoted procedure BiRel_Left($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($left("<<-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure Rel_R($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($right("-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure +!unquoted procedure Rel_Right($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($right("-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure BiRel_R($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($right("<<-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure +!unquoted procedure BiRel_Right($from, $to, $label, $techn="", $descr="", $sprite="", $tags="", $link="") +$getRel($right("<<-","->>"), $from, $to, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +' Layout Helpers +' ################################## + +!function $getHiddenLine($distance) + !return '-[hidden]' + %substr('------------', 0, %intval($distance) + 1) +!endfunction + +!function $l_down($start,$end) + !if (NO_LAY_ROTATE == 0) + !return $down($start,$end) + !else + !return $start+"DOWN"+$end + !endif +!endfunction + +!function $l_up($start,$end) + !if (NO_LAY_ROTATE == 0) + !return $up($start,$end) + !else + !return $start+"UP"+$end + !endif +!endfunction + +!function $l_left($start,$end) + !if (NO_LAY_ROTATE == 0) + !return $left($start,$end) + !else + !return $start+"LEFT"+$end + !endif +!endfunction + +!function $l_right($start,$end) + !if (NO_LAY_ROTATE == 0) + !return $right($start,$end) + !else + !return $start+"RIGHT"+$end + !endif +!endfunction + +!unquoted procedure Lay_D($from, $to) +$from $l_down("-[hidden]","-") $to +!endprocedure +!unquoted procedure Lay_Down($from, $to) +$from $l_down("-[hidden]","-") $to +!endprocedure + +!unquoted procedure Lay_U($from, $to) +$from $l_up("-[hidden]","-") $to +!endprocedure +!unquoted procedure Lay_Up($from, $to) +$from $l_up("-[hidden]","-") $to +!endprocedure + +!unquoted procedure Lay_R($from, $to) +$from $l_right("-[hidden]","-") $to +!endprocedure +!unquoted procedure Lay_Right($from, $to) +$from $l_right("-[hidden]","-") $to +!endprocedure + +!unquoted procedure Lay_L($from, $to) +$from $l_left("-[hidden]","-") $to +!endprocedure +!unquoted procedure Lay_Left($from, $to) +$from $l_left("-[hidden]","-") $to +!endprocedure + +' PlantUML bug: lines which does "not match" with the orientation/direction of the diagram +' use the same length therefore the method offers no direction at all. +' If a direction is required the Lay_...() methods can be used +!unquoted procedure Lay_Distance($from, $to, $distance="0") +$from $getHiddenLine($distance) $to +!endprocedure diff --git a/assets/diagrams/c4/C4_Component.puml b/assets/diagrams/c4/C4_Component.puml new file mode 100644 index 0000000..7bf119f --- /dev/null +++ b/assets/diagrams/c4/C4_Component.puml @@ -0,0 +1,105 @@ +' convert it with additional command line argument -DRELATIVE_INCLUDE="relative/absolute" to use locally +!if %variable_exists("RELATIVE_INCLUDE") + !include ./C4_Container.puml +!else + !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml +!endif + +' Scope: A single container. +' Primary elements: Components within the container in scope. +' Supporting elements: Containers (within the software system in scope) plus people and software systems directly connected to the components. +' Intended audience: Software architects and developers. + +' Colors +' ################################## + +!$COMPONENT_FONT_COLOR ?= "#000000" +!$COMPONENT_BG_COLOR ?= "#85BBF0" +!$COMPONENT_BORDER_COLOR ?= "#78A8D8" + +!$EXTERNAL_COMPONENT_LEGEND_TEXT ?= "external component" +!$EXTERNAL_COMPONENT_FONT_COLOR ?= $COMPONENT_FONT_COLOR +!$EXTERNAL_COMPONENT_BG_COLOR ?= "#CCCCCC" +!$EXTERNAL_COMPONENT_BORDER_COLOR ?= "#BFBFBF" + +' New C4 style automatically swaps font and background colors, but with those settings the component/node background would be black. +' Therefore the colors should be displayed unchanged (the init colors have to be swapped too). +!$NEW_C4_USE_ORIGINAL_COMPONENT_COLORS ?= 1 +!$componentColorsSwappedAlready ?= 0 +!if (NEW_C4_STYLE == 1 && $NEW_C4_USE_ORIGINAL_COMPONENT_COLORS == 1 && $componentColorsSwappedAlready == 0) + !$swap=$COMPONENT_BG_COLOR + !$COMPONENT_BG_COLOR=$COMPONENT_FONT_COLOR + !$COMPONENT_FONT_COLOR=$swap + !$swap=$EXTERNAL_COMPONENT_BG_COLOR + !$EXTERNAL_COMPONENT_BG_COLOR=$EXTERNAL_COMPONENT_FONT_COLOR + !$EXTERNAL_COMPONENT_FONT_COLOR=$swap + ' don't swap them again below if C4_... included again + !$componentColorsSwappedAlready=1 +!endif + +' Labels +' ################################## + +!$COMPONENT_LEGEND_TEXT ?= "component" +!$EXTERNAL_COMPONENT_LEGEND_TEXT ?= "external component" + +' Styling +' ################################## + +UpdateElementStyle("component", $COMPONENT_BG_COLOR, $COMPONENT_FONT_COLOR, $COMPONENT_BORDER_COLOR, $shape=$DEFAULT_SHAPE, $legendText="$COMPONENT_LEGEND_TEXT") +UpdateElementStyle("external_component", $EXTERNAL_COMPONENT_BG_COLOR, $EXTERNAL_COMPONENT_FONT_COLOR, $EXTERNAL_COMPONENT_BORDER_COLOR, $shape=$DEFAULT_SHAPE, $legendText="$EXTERNAL_COMPONENT_LEGEND_TEXT") + +' shortcuts with default colors +!unquoted procedure AddComponentTag($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $techn="", $legendText="", $legendSprite="", $borderStyle="", $borderThickness="") + $addElementTagInclReuse("component", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $techn, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure +!unquoted procedure AddExternalComponentTag($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $techn="", $legendText="", $legendSprite="", $borderStyle="", $borderThickness="") + $addElementTagInclReuse("external_component", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $techn, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure + +' Layout +' ################################## + +SetDefaultLegendEntries("person\nsystem\ncontainer\ncomponent\nexternal_person\nexternal_system\nexternal_container\nexternal_component\nenterprise_boundary\nsystem_boundary\ncontainer_boundary\nboundary") + +!procedure LAYOUT_WITH_LEGEND() +hide stereotype +legend right +|**Legend** | +|<$PERSON_BG_COLOR> person | +|<$SYSTEM_BG_COLOR> system | +|<$CONTAINER_BG_COLOR> container | +|<$COMPONENT_BG_COLOR> component | +|<$EXTERNAL_PERSON_BG_COLOR> external person | +|<$EXTERNAL_SYSTEM_BG_COLOR> external system | +|<$EXTERNAL_CONTAINER_BG_COLOR> external container | +|<$EXTERNAL_COMPONENT_BG_COLOR> external component | +endlegend +!endprocedure + +' Elements +' ################################## + +!unquoted procedure Component($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="", $baseShape="rectangle") + $getElementLine($baseShape, "component", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure ComponentDb($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="") + $getElementLine("database", "component", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure ComponentQueue($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="") + $getElementLine("queue", "component", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure Component_Ext($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="", $baseShape="rectangle") + $getElementLine($baseShape, "external_component", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure ComponentDb_Ext($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="") + $getElementLine("database", "external_component", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure ComponentQueue_Ext($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="") + $getElementLine("queue", "external_component", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure diff --git a/assets/diagrams/c4/C4_Container.puml b/assets/diagrams/c4/C4_Container.puml new file mode 100644 index 0000000..2977fdb --- /dev/null +++ b/assets/diagrams/c4/C4_Container.puml @@ -0,0 +1,111 @@ +' convert it with additional command line argument -DRELATIVE_INCLUDE="relative/absolute" to use locally +!if %variable_exists("RELATIVE_INCLUDE") + !include ./C4_Context.puml +!else + !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml +!endif + +' Scope: A single software system. +' Primary elements: Containers within the software system in scope. +' Supporting elements: People and software systems directly connected to the containers. +' Intended audience: Technical people inside and outside of the software development team; including software architects, developers and operations/support staff. + +' Colors +' ################################## + +!$CONTAINER_FONT_COLOR ?= $ELEMENT_FONT_COLOR +!$CONTAINER_BG_COLOR ?= "#438DD5" +!$CONTAINER_BORDER_COLOR ?= "#3C7FC0" + +!$CONTAINER_BOUNDARY_COLOR ?= $BOUNDARY_COLOR +!$CONTAINER_BOUNDARY_BG_COLOR ?= $BOUNDARY_BG_COLOR +!$CONTAINER_BOUNDARY_BORDER_STYLE ?= $BOUNDARY_BORDER_STYLE + +!$EXTERNAL_CONTAINER_FONT_COLOR ?= $ELEMENT_FONT_COLOR +!$EXTERNAL_CONTAINER_BG_COLOR ?= "#B3B3B3" +!$EXTERNAL_CONTAINER_BORDER_COLOR ?= "#A6A6A6" + +' Labels +' ################################## + +!$CONTAINER_LEGEND_TEXT ?= "container" +!$CONTAINER_BOUNDARY_TYPE ?= "container" +!$CONTAINER_BOUNDARY_LEGEND_TEXT ?= "container boundary" +!$EXTERNAL_CONTAINER_LEGEND_TEXT ?= "external container" + +' Styling +' ################################## +UpdateElementStyle("container", $CONTAINER_BG_COLOR, $CONTAINER_FONT_COLOR, $CONTAINER_BORDER_COLOR, $shape=$DEFAULT_SHAPE, $legendText="$CONTAINER_LEGEND_TEXT") +UpdateElementStyle("external_container", $EXTERNAL_CONTAINER_BG_COLOR, $EXTERNAL_CONTAINER_FONT_COLOR, $EXTERNAL_CONTAINER_BORDER_COLOR, $shape=$DEFAULT_SHAPE, $legendText="$EXTERNAL_CONTAINER_LEGEND_TEXT") + +UpdateBoundaryStyle("container", $bgColor=$CONTAINER_BOUNDARY_BG_COLOR, $fontColor=$CONTAINER_BOUNDARY_COLOR, $borderColor=$CONTAINER_BOUNDARY_COLOR, $type="$CONTAINER_BOUNDARY_TYPE", $shape=$DEFAULT_SHAPE, $legendText="$CONTAINER_BOUNDARY_LEGEND_TEXT") + +' shortcuts with default colors +!unquoted procedure AddContainerTag($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $techn="", $legendText="", $legendSprite="", $borderStyle="", $borderThickness="") + $addElementTagInclReuse("container", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $techn, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure +!unquoted procedure AddExternalContainerTag($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $techn="", $legendText="", $legendSprite="", $borderStyle="", $borderThickness="") + $addElementTagInclReuse("external_container", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $techn, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure + +!unquoted procedure UpdateContainerBoundaryStyle($bgColor=$CONTAINER_BOUNDARY_BG_COLOR, $fontColor=$CONTAINER_BOUNDARY_COLOR, $borderColor=$CONTAINER_BOUNDARY_COLOR, $shadowing="", $shape="", $type="Container", $legendText="", $borderStyle="", $borderThickness="", $sprite="", $legendSprite="") + UpdateBoundaryStyle("container", $bgColor, $fontColor, $borderColor, $shadowing, $shape, $type, $legendText, $borderStyle, $borderThickness, $sprite, $legendSprite) +!endprocedure + +' Layout +' ################################## + +SetDefaultLegendEntries("person\nsystem\ncontainer\nexternal_person\nexternal_system\nexternal_container\nenterprise_boundary\nsystem_boundary\ncontainer_boundary\nboundary") + +!procedure LAYOUT_WITH_LEGEND() +hide stereotype +legend right +|**Legend** | +|<$PERSON_BG_COLOR> person | +|<$SYSTEM_BG_COLOR> system | +|<$CONTAINER_BG_COLOR> container | +|<$EXTERNAL_PERSON_BG_COLOR> external person | +|<$EXTERNAL_SYSTEM_BG_COLOR> external system | +|<$EXTERNAL_CONTAINER_BG_COLOR> external container | +endlegend +!endprocedure + +' Elements +' ################################## + +!unquoted procedure Container($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="", $baseShape="rectangle") + $getElementLine($baseShape , "container", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure ContainerDb($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="") + $getElementLine("database", "container", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure ContainerQueue($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="") + $getElementLine("queue", "container", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure Container_Ext($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="", $baseShape="rectangle") + $getElementLine($baseShape , "external_container", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure ContainerDb_Ext($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="") + $getElementLine("database", "external_container", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure ContainerQueue_Ext($alias, $label, $techn="", $descr="", $sprite="", $tags="", $link="") + $getElementLine("queue", "external_container", $alias, $label, $techn, $descr, $sprite, $tags, $link) +!endprocedure + +' Boundaries +' ################################## + +!unquoted procedure Container_Boundary($alias, $label, $tags="", $link="", $descr = "") + !if ($tags != "") + !$allTags = $tags + '+container' + !else + !$allTags = 'container' + !endif + ' $type defined via $tag style + Boundary($alias, $label, "", $allTags, $link, $descr) +!endprocedure diff --git a/assets/diagrams/c4/C4_Context.puml b/assets/diagrams/c4/C4_Context.puml new file mode 100644 index 0000000..4c4407e --- /dev/null +++ b/assets/diagrams/c4/C4_Context.puml @@ -0,0 +1,454 @@ +' convert it with additional command line argument -DRELATIVE_INCLUDE="relative/absolute" to use locally +!if %variable_exists("RELATIVE_INCLUDE") + !include ./C4.puml +!else + !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4.puml +!endif + +' Scope: A single software system. +' Primary elements: The software system in scope. +' Supporting elements: People and software systems directly connected to the software system in scope. +' Intended audience: Everybody, both technical and non-technical people, inside and outside of the software development team. + +' Colors +' ################################## + +!$PERSON_FONT_COLOR ?= $ELEMENT_FONT_COLOR +!$PERSON_BG_COLOR ?= "#08427B" +!$PERSON_BORDER_COLOR ?= "#073B6F" + +!$EXTERNAL_PERSON_FONT_COLOR ?= $ELEMENT_FONT_COLOR +!$EXTERNAL_PERSON_BG_COLOR ?= "#686868" +!$EXTERNAL_PERSON_BORDER_COLOR ?= "#8A8A8A" + +!$SYSTEM_FONT_COLOR ?= $ELEMENT_FONT_COLOR +!$SYSTEM_BG_COLOR ?= "#1168BD" +!$SYSTEM_BORDER_COLOR ?= "#3C7FC0" + +!$SYSTEM_BOUNDARY_COLOR ?= $BOUNDARY_COLOR +!$SYSTEM_BOUNDARY_BG_COLOR ?= $BOUNDARY_BG_COLOR +!$SYSTEM_BOUNDARY_BORDER_STYLE ?= $BOUNDARY_BORDER_STYLE + +!$EXTERNAL_SYSTEM_FONT_COLOR ?= $ELEMENT_FONT_COLOR +!$EXTERNAL_SYSTEM_BG_COLOR ?= "#999999" +!$EXTERNAL_SYSTEM_BORDER_COLOR ?= "#8A8A8A" + +!$ENTERPRISE_BOUNDARY_COLOR ?= $BOUNDARY_COLOR +!$ENTERPRISE_BOUNDARY_BG_COLOR ?= $BOUNDARY_BG_COLOR +!$ENTERPRISE_BOUNDARY_BORDER_STYLE ?= $BOUNDARY_BORDER_STYLE + +' Labels +' ################################## + +!$PERSON_LEGEND_TEXT ?= "person" +!$EXTERNAL_PERSON_LEGEND_TEXT ?= "external person" + +!$SYSTEM_LEGEND_TEXT ?= "system" +!$SYSTEM_BOUNDARY_TYPE ?= "system" +!$SYSTEM_BOUNDARY_LEGEND_TEXT ?= "system boundary" +!$EXTERNAL_SYSTEM_LEGEND_TEXT ?= "external system" + +!$ENTERPRISE_BOUNDARY_TYPE ?= "enterprise" +!$ENTERPRISE_BOUNDARY_LEGEND_TEXT ?= "enterprise boundary" + +' Styling +' ################################## + +UpdateElementStyle("person", $PERSON_BG_COLOR, $PERSON_FONT_COLOR, $PERSON_BORDER_COLOR, $shape=$DEFAULT_SHAPE, $legendText="$PERSON_LEGEND_TEXT") +UpdateElementStyle("external_person", $EXTERNAL_PERSON_BG_COLOR, $EXTERNAL_PERSON_FONT_COLOR, $EXTERNAL_PERSON_BORDER_COLOR, $shape=$DEFAULT_SHAPE, $legendText="$EXTERNAL_PERSON_LEGEND_TEXT") +UpdateElementStyle("system", $SYSTEM_BG_COLOR, $SYSTEM_FONT_COLOR, $SYSTEM_BORDER_COLOR, $shape=$DEFAULT_SHAPE, $legendText="$SYSTEM_LEGEND_TEXT") +UpdateElementStyle("external_system", $EXTERNAL_SYSTEM_BG_COLOR, $EXTERNAL_SYSTEM_FONT_COLOR, $EXTERNAL_SYSTEM_BORDER_COLOR, $shape=$DEFAULT_SHAPE, $legendText="$EXTERNAL_SYSTEM_LEGEND_TEXT") + +UpdateBoundaryStyle("system", $bgColor=$SYSTEM_BOUNDARY_BG_COLOR, $fontColor=$SYSTEM_BOUNDARY_COLOR, $borderColor=$SYSTEM_BOUNDARY_COLOR, $type="$SYSTEM_BOUNDARY_TYPE", $shape=$DEFAULT_SHAPE, $legendText="$SYSTEM_BOUNDARY_LEGEND_TEXT") +UpdateBoundaryStyle("enterprise", $bgColor=$ENTERPRISE_BOUNDARY_BG_COLOR, $fontColor=$ENTERPRISE_BOUNDARY_COLOR, $borderColor=$ENTERPRISE_BOUNDARY_COLOR, $type="$ENTERPRISE_BOUNDARY_TYPE", $shape=$DEFAULT_SHAPE, $legendText="$ENTERPRISE_BOUNDARY_LEGEND_TEXT") + +' shortcuts with default colors +!unquoted procedure AddPersonTag($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $legendText="", $legendSprite="", $type="", $borderStyle="", $borderThickness="") + $addElementTagInclReuse("person", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $type, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure +!unquoted procedure AddExternalPersonTag($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $legendText="", $legendSprite="", $type="", $borderStyle="", $borderThickness="") + $addElementTagInclReuse("external_person", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $type, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure +!unquoted procedure AddSystemTag($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $legendText="", $legendSprite="", $type="", $borderStyle="", $borderThickness="") + $addElementTagInclReuse("system", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $type, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure +!unquoted procedure AddExternalSystemTag($tagStereo, $bgColor="", $fontColor="", $borderColor="", $shadowing="", $shape="", $sprite="", $legendText="", $legendSprite="", $type="", $borderStyle="", $borderThickness="") + $addElementTagInclReuse("external_system", $tagStereo, $bgColor, $fontColor, $borderColor, $shadowing, $shape, $sprite, $type, $legendText, $legendSprite, $borderStyle, $borderThickness) +!endprocedure + +!unquoted procedure UpdateEnterpriseBoundaryStyle($bgColor=$ENTERPRISE_BOUNDARY_BG_COLOR, $fontColor=$ENTERPRISE_BOUNDARY_COLOR, $borderColor=$ENTERPRISE_BOUNDARY_COLOR, $shadowing="", $shape="", $type="Enterprise", $legendText="", $borderStyle="", $borderThickness="", $sprite="", $legendSprite="") + UpdateBoundaryStyle("enterprise", $bgColor, $fontColor, $borderColor, $shadowing, $shape, $type, $legendText, $borderStyle, $borderThickness, $sprite, $legendSprite) +!endprocedure +!unquoted procedure UpdateSystemBoundaryStyle($bgColor=$SYSTEM_BOUNDARY_BG_COLOR, $fontColor=$SYSTEM_BOUNDARY_COLOR, $borderColor=$SYSTEM_BOUNDARY_COLOR, $shadowing="", $shape="", $type="System", $legendText="", $borderStyle="", $borderThickness="", $sprite="", $legendSprite="") + UpdateBoundaryStyle("system", $bgColor, $fontColor, $borderColor, $shadowing, $shape, $type, $legendText, $borderStyle, $borderThickness, $sprite, $legendSprite) +!endprocedure + +' Sprites +' ################################## + +sprite $person [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +0000000000000000000049BCCA7200000000000000000000 +0000000000000000006EFFFFFFFFB3000000000000000000 +00000000000000001CFFFFFFFFFFFF700000000000000000 +0000000000000001EFFFFFFFFFFFFFF80000000000000000 +000000000000000CFFFFFFFFFFFFFFFF6000000000000000 +000000000000007FFFFFFFFFFFFFFFFFF100000000000000 +00000000000001FFFFFFFFFFFFFFFFFFF900000000000000 +00000000000006FFFFFFFFFFFFFFFFFFFF00000000000000 +0000000000000BFFFFFFFFFFFFFFFFFFFF40000000000000 +0000000000000EFFFFFFFFFFFFFFFFFFFF70000000000000 +0000000000000FFFFFFFFFFFFFFFFFFFFF80000000000000 +0000000000000FFFFFFFFFFFFFFFFFFFFF80000000000000 +0000000000000DFFFFFFFFFFFFFFFFFFFF60000000000000 +0000000000000AFFFFFFFFFFFFFFFFFFFF40000000000000 +00000000000006FFFFFFFFFFFFFFFFFFFE00000000000000 +00000000000000EFFFFFFFFFFFFFFFFFF800000000000000 +000000000000007FFFFFFFFFFFFFFFFFF100000000000000 +000000000000000BFFFFFFFFFFFFFFFF5000000000000000 +0000000000000001DFFFFFFFFFFFFFF70000000000000000 +00000000000000000BFFFFFFFFFFFF500000000000000000 +0000000000000000005DFFFFFFFFA1000000000000000000 +0000000000000000000037ABB96100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000025788300000000005886410000000000000 +000000000007DFFFFFFD9643347BFFFFFFFB400000000000 +0000000004EFFFFFFFFFFFFFFFFFFFFFFFFFFB1000000000 +000000007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFD200000000 +00000006FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE10000000 +0000003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0000000 +000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5000000 +000003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD000000 +000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF200000 +00000DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF600000 +00000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF800000 +00001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA00000 +00001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB00000 +00001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB00000 +00001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB00000 +00001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA00000 +00000EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF700000 +000006FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE100000 +0000008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3000000 +000000014555555555555555555555555555555300000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $person2 [48x48/16] { +0000000000000000000049BCCA7200000000000000000000 +0000000000000000006EFFFFFFFFB3000000000000000000 +00000000000000001CFFFFFFFFFFFF700000000000000000 +0000000000000001EFFFFFFFFFFFFFF80000000000000000 +000000000000000CFFFFFFFFFFFFFFFF6000000000000000 +000000000000007FFFFFFFFFFFFFFFFFF100000000000000 +00000000000001FFFFFFFFFFFFFFFFFFF900000000000000 +00000000000006FFFFFFFFFFFFFFFFFFFF00000000000000 +0000000000000BFFFFFFFFFFFFFFFFFFFF40000000000000 +0000000000000EFFFFFFFFFFFFFFFFFFFF70000000000000 +0000000000000FFFFFFFFFFFFFFFFFFFFF80000000000000 +0000000000000FFFFFFFFFFFFFFFFFFFFF80000000000000 +0000000000000DFFFFFFFFFFFFFFFFFFFF60000000000000 +0000000000000AFFFFFFFFFFFFFFFFFFFF40000000000000 +00000000000006FFFFFFFFFFFFFFFFFFFE00000000000000 +00000000000000EFFFFFFFFFFFFFFFFFF800000000000000 +000000000000007FFFFFFFFFFFFFFFFFF100000000000000 +000000000000000BFFFFFFFFFFFFFFFF5000000000000000 +0000000000000001DFFFFFFFFFFFFFF70000000000000000 +00000000000000000BFFFFFFFFFFFF500000000000000000 +0000000000000000005DFFFFFFFFA1000000000000000000 +0000000000000000000037ABB96100000000000000000000 +000000000002578888300000000005888864100000000000 +0000000007DFFFFFFFFD9643347BFFFFFFFFFB4000000000 +00000004EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB10000000 +0000007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2000000 +000006FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE100000 +00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB00000 +0000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF50000 +0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0000 +0009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2000 +000DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6000 +000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB000 +001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB000 +001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB000 +001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA000 +000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +000DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6000 +0009FFFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFFF2000 +0003FFFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFFD0000 +0000BFFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFF50000 +00003FFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFB00000 +000006FFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFE100000 +0000007FFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFD2000000 +00000004EFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFB10000000 +0000000007DF8FFFFFFFFFFFFFFFFFFFFFF8FB4000000000 +000000000002578888888888888888888864100000000000 +} + +sprite $robot [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000005BFFFFFFFFFFFFFFFFFFFFFE9100000000000 +0000000000AFFFFFFFFFFFFFFFFFFFFFFFFFE30000000000 +0000000007FFFFFFFFFFFFFFFFFFFFFFFFFFFE1000000000 +000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000000000 +000000004FFFFFFFFFFFFFFFFFFFFFFFFFFFFFC000000000 +000000005FFFFFFFFFFFFFFFFFFFFFFFFFFFFFD000000000 +000000005FFFFFFFFFFFFFFFFFFFFFFFFFFFFFE000000000 +000000005FFFFFFFFFFFFFFFFFFFFFFFFFFFFFE000000000 +000699405FFFFFFC427FFFFFFFFFC427FFFFFFE009982000 +008FFF705FFFFFE10006FFFFFFFE00007FFFFFE00FFFF100 +00CFFF705FFFFFA00001FFFFFFF900002FFFFFE00FFFF500 +00DFFF705FFFFFB00002FFFFFFFA00003FFFFFE00FFFF500 +00DFFF705FFFFFF4000AFFFFFFFF3000BFFFFFE00FFFF500 +00DFFF705FFFFFFFA8DFFFFFFFFFFA8DFFFFFFE00FFFF500 +00DFFF705FFFFFFFFFFFFFFFFFFFFFFFFFFFFFE00FFFF500 +00DFFF705FFFFFFFFFFFFFFFFFFFFFFFFFFFFFE00FFFF500 +00DFFF705FFFFFFFFFFFFFFFFFFFFFFFFFFFFFE00FFFF500 +00DFFF705FFFFFFFFFFFFFFFFFFFFFFFFFFFFFE00FFFF500 +00DFFF705FFFFFFFFFFFFFFFFFFFFFFFFFFFFFE00FFFF500 +00CFFF705FFFFFF87777777777777777CFFFFFE00FFFF500 +008FFF705FFFFFF100000000000000009FFFFFE00FFFF100 +000699405FFFFFF76666666666666666CFFFFFE009982000 +000000005FFFFFFFFFFFFFFFFFFFFFFFFFFFFFE000000000 +000000005FFFFFFFFFFFFFFFFFFFFFFFFFFFFFE000000000 +000000004FFFFFFFFFFFFFFFFFFFFFFFFFFFFFC000000000 +000000000EFFFFFFFFFFFFFFFFFFFFFFFFFFFF7000000000 +0000000005FFFFFFFFFFFFFFFFFFFFFFFFFFFD0000000000 +00000000004CFFFFFFFFFFFFFFFFFFFFFFFF910000000000 +000000000000011111111111111111111110000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $robot2 [48x48/16] { +000000000000000088888888888888880000000000000000 +000000000000000AFFFFFFFFFFFFFFFFA000000000000000 +00000000000000CFFFFFFFFFFFFFFFFFFC00000000000000 +00000000000004EFFFFFFFFFFFFFFFFFFE40000000000000 +0000000000000AFFFFFFFFFFFFFFFFFFFFA0000000000000 +00000000000008FFFFFFFFFFFFFFFFFFFF80000000000000 +00000000000008FFFFFFFFFFFFFFFFFFFF80000000000000 +00000000000008FFFFFFFFFFFFFFFFFFFF80000000000000 +00000000000888FFFFFFFFFFFFFFFFFFFF88800000000000 +00000000008FF8FFFFFFFFFFFFFFFFFFFF8FF80000000000 +00000000008FF8FFFFFFFFFFFFFFFFFFFF8FF80000000000 +00000000008FF8FFFFFFFFFFFFFFFFFFFF8FF80000000000 +00000000008FF8FFFFFFFFFFFFFFFFFFFF8FF80000000000 +00000000008FF8FFFFFFFFFFFFFFFFFFFF8FF80000000000 +00000000008FF8FFFFFFFFFFFFFFFFFFFF8FF80000000000 +00000000000888FFFFFFFFFFFFFFFFFFFF88800000000000 +00000000000008FFFFFFFFFFFFFFFFFFFF80000000000000 +00000000000008FFFFFFFFFFFFFFFFFFFF80000000000000 +00000000000008FFFFFFFFFFFFFFFFFFFF80000000000000 +00000000000008FFFFFFFFFFFFFFFFFFFF80000000000000 +00000000000008FFFFFFFFFFFFFFFFFFFF80000000000000 +00000000000004CFFFFFFFFFFFFFFFFFFC40000000000000 +000000488888848CFFFFFFFFFFFFFFFFC848888884000000 +00000CFFFFFFFFC888888888888888888CFFFFFFFFC00000 +00008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000 +0000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000 +0008FFFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFFF8000 +0008FFFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFFF8000 +0008FFFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFFF8000 +0008FFFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFFF8000 +0000CFFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFFC0000 +00008FFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFF80000 +00000CFFFFFF8FFFFFFFFFFFFFFFFFFFFFF8FFFFFFC00000 +000000488887578888888888888888888864688884000000 +000000000000000000000000000000000000000000000000 +} + +' Layout +' ################################## + +SetDefaultLegendEntries("person\nsystem\nexternal_person\nexternal_system\nenterprise_boundary\nsystem_boundary\nboundary") + +!procedure LAYOUT_WITH_LEGEND() +hide stereotype +legend right +|**Legend** | +|<$PERSON_BG_COLOR> person | +|<$SYSTEM_BG_COLOR> system| +|<$EXTERNAL_PERSON_BG_COLOR> external person | +|<$EXTERNAL_SYSTEM_BG_COLOR> external system | +endlegend +!endprocedure + +!global $defaultPersonSprite = "person" +!$dummy = $restoreEmpty("person", "sprite", $defaultPersonSprite, %true()) +UpdateElementStyle("person") +' workaround of plantuml.jar bug - person overwrites external_person setting +!$dummy = $restoreEmpty("external_person", "sprite", $defaultPersonSprite, %true()) +UpdateElementStyle("external_person") +!global $portraitPerson = "false" + +!procedure $clearPersonRestore() + !$dummy = $clearRestore("person", "sprite") + !$dummy = $clearRestore("person", "legendSprite") + %set_variable_value("$" + "person" + "ElementTagSprite", "") + UpdateElementStyle("person") + ' workaround of plantuml.jar bug - person overwrites external_person setting + !$dummy = $clearRestore("external_person", "sprite") + !$dummy = $clearRestore("external_person", "legendSprite") + %set_variable_value("$" + "external_person" + "ElementTagSprite", "") + UpdateElementStyle("external_person") +!endprocedure + +!procedure HIDE_PERSON_SPRITE() + !$defaultPersonSprite = "" + !$portraitPerson = "false" + $clearPersonRestore() +!endprocedure + +!unquoted procedure SHOW_PERSON_SPRITE($sprite="") + !if ($sprite == "") + !$defaultPersonSprite = "person" + !else + !$defaultPersonSprite = $sprite + !endif + !$dummy = $restoreEmpty("person", "sprite", $defaultPersonSprite, %true()) + UpdateElementStyle("person") + ' workaround of plantuml.jar bug - person overwrites external_person setting + !$dummy = $restoreEmpty("external_person", "sprite", $defaultPersonSprite, %true()) + UpdateElementStyle("external_person") + !$portraitPerson = "false" +!endprocedure + +!unquoted procedure SHOW_PERSON_PORTRAIT() + !$defaultPersonSprite = "" + !$portraitPerson = "portrait" + $clearPersonRestore() +!endprocedure + +!unquoted procedure SHOW_PERSON_OUTLINE() + !$defaultPersonSprite = "" + !$portraitPerson = "outline" + $clearPersonRestore() +!endprocedure + +' Elements +' ################################## + +!function $getPerson($label, $type, $descr, $sprite) + !if ($sprite == "") && ($defaultPersonSprite != "") + !$sprite = $defaultPersonSprite + !endif + !return $getElementBase($label, $type, $descr, $sprite) +!endfunction + +!unquoted procedure Person($alias, $label, $descr="", $sprite="", $tags="", $link="", $type="") +!$sprite=$toElementArg($sprite, $tags, "ElementTagSprite", "person") +' $type reuses $techn definition of $tags +!$type=$toElementArg($type, $tags, "ElementTagTechn", "person") +!if ($showPersonTypeViaTech == %true()) + !$type=$updateTechWithElementType($type, "person") +!endif +!if ($portraitPerson == "portrait") && ($sprite == "") +actor "$getPerson($label, $type, $descr, $sprite)$getProps()" $toStereos("person", $tags) as $alias $getLink($link) +!elseif ($portraitPerson == "outline") && ($sprite == "") +person "$getPerson($label, $type, $descr, $sprite)$getProps()" $toStereos("person", $tags) as $alias $getLink($link) +!else +rectangle "$getPerson($label, $type, $descr, $sprite)$getProps()" $toStereos("person", $tags) as $alias $getLink($link) +!endif +!endprocedure + +!unquoted procedure Person_Ext($alias, $label, $descr="", $sprite="", $tags="", $link="", $type="") +!$sprite=$toElementArg($sprite, $tags, "ElementTagSprite", "external_person") +' $type reuses $techn definition of $tags +!$type=$toElementArg($type, $tags, "ElementTagTechn", "external_person") +!if ($showPersonTypeViaTech == %true()) + !$type=$updateTechWithElementType($type, "external_person") +!endif +!if ($portraitPerson == "portrait") && ($sprite == "") +actor "$getPerson($label, $type, $descr, $sprite)$getProps()" $toStereos("external_person", $tags) as $alias $getLink($link) +!elseif ($portraitPerson == "outline") && ($sprite == "") +person "$getPerson($label, $type, $descr, $sprite)$getProps()" $toStereos("external_person", $tags) as $alias $getLink($link) +!else +rectangle "$getPerson($label, $type, $descr, $sprite)$getProps()" $toStereos("external_person", $tags) as $alias $getLink($link) +!endif +!endprocedure + +!unquoted procedure System($alias, $label, $descr="", $sprite="", $tags="", $link="", $type="", $baseShape="rectangle") + ' $type reuses $techn definition of $tags + $getElementLine($baseShape, "system", $alias, $label, $type, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure SystemDb($alias, $label, $descr="", $sprite="", $tags="", $link="", $type="") + ' $type reuses $techn definition of $tags + $getElementLine("database", "system", $alias, $label, $type, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure SystemQueue($alias, $label, $descr="", $sprite="", $tags="", $link="", $type="") + ' $type reuses $techn definition of $tags + $getElementLine("queue", "system", $alias, $label, $type, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure System_Ext($alias, $label, $descr="", $sprite="", $tags="", $link="", $type="", $baseShape="rectangle") + ' $type reuses $techn definition of $tags + $getElementLine($baseShape , "external_system", $alias, $label, $type, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure SystemDb_Ext($alias, $label, $descr="", $sprite="", $tags="", $link="", $type="") + ' $type reuses $techn definition of $tags + $getElementLine("database", "external_system", $alias, $label, $type, $descr, $sprite, $tags, $link) +!endprocedure + +!unquoted procedure SystemQueue_Ext($alias, $label, $descr="", $sprite="", $tags="", $link="", $type="") + ' $type reuses $techn definition of $tags + $getElementLine("queue", "external_system", $alias, $label, $type, $descr, $sprite, $tags, $link) +!endprocedure + +' Boundaries +' ################################## + +!unquoted procedure Enterprise_Boundary($alias, $label, $tags="", $link="", $descr = "") + !if ($tags != "") + !$allTags = $tags + '+enterprise' + !else + !$allTags = 'enterprise' + !endif + ' $type defined via $tag style + Boundary($alias, $label, "", $allTags, $link, $descr) +!endprocedure + +!unquoted procedure System_Boundary($alias, $label, $tags="", $link="", $descr = "") + !if ($tags != "") + !$allTags = $tags + '+system' + !else + !$allTags = 'system' + !endif + ' $type defined via $tag style + Boundary($alias, $label, "", $allTags, $link, $descr) +!endprocedure diff --git a/assets/diagrams/discovery/discovery-class.png b/assets/diagrams/discovery/discovery-class.png new file mode 100644 index 0000000..c7f363f Binary files /dev/null and b/assets/diagrams/discovery/discovery-class.png differ diff --git a/assets/diagrams/discovery/discovery-class.puml b/assets/diagrams/discovery/discovery-class.puml new file mode 100644 index 0000000..e210182 --- /dev/null +++ b/assets/diagrams/discovery/discovery-class.puml @@ -0,0 +1,203 @@ +@startuml discovery-class +!include ../styles/reqsai-class-style.puml + +top to bottom direction + +title Req Discovery BC -- Tactical Class Diagram + +legend top right + |= Elemento |= Significado | + |<$REQSAI_NAVY_800> <> | Raiz de aggregate | + |<$REQSAI_GREEN_900> <> | Record inmutable y validado | + |<$REQSAI_VIOLET_900> <> | Enumeracion de dominio | + |<$REQSAI_BROWN_900> <> | Evento de dominio (inmutable) | + |<$REQSAI_GRAY_700> <> | Clase abstracta Shared Kernel | + |Relacion|Significado| + | --\\|> | extends (herencia) | + | ..> | uses / depends on | + | o-- | referencia por ID (cross-aggregate) | + | ..> <>| evento publicado por el aggregate | +endlegend + +package "Shared Kernel" as SharedKernel { + abstract class AbstractAggregateRoot <> + abstract class EntityNotFoundException <> + abstract class BusinessRuleViolationException <> +} + +package "Req Discovery" { + + package "Domain" { + package "Model" { + package "Aggregates" as DiscAggregates { + + class DiscoverySession <> { + - id : DiscoverySessionId + - projectId : ProjectId + - title : String + - transcript : String + - status : SessionStatus + - processingError : String + -- + # DiscoverySession() + + DiscoverySession(projectId : ProjectId, title : String) + ..Business Methods.. + + uploadTranscript(transcript : String) : void + + startProcessing() : void + + complete() : void + + fail(error : String) : void + + reset() : void + ..Query Methods.. + + isCompleted() : boolean + + isProcessing() : boolean + + hasFailed() : boolean + } + + class UserStory <> { + - id : UserStoryId + - sessionId : DiscoverySessionId + - projectId : ProjectId + - title : String + - role : String + - action : String + - benefit : String + - priority : Priority + - storyPoints : Integer + - status : StoryStatus + - acceptanceCriteria : List + -- + # UserStory() + + UserStory(sessionId : DiscoverySessionId, projectId : ProjectId, title : String, role : String, action : String, benefit : String, priority : Priority) + ..Business Methods.. + + approve() : void + + reject() : void + + updatePriority(priority : Priority) : void + + updateStoryPoints(points : Integer) : void + + addCriterion(description : String, type : CriterionType) : void + + removeCriterion(criterionId : AcceptanceCriterionId) : void + ..Query Methods.. + + isDraft() : boolean + + isApproved() : boolean + + isRejected() : boolean + } + } + + package "Entities" as DiscEntities { + class AcceptanceCriterion { + - id : AcceptanceCriterionId + - storyId : UserStoryId + - description : String + - type : CriterionType + -- + + AcceptanceCriterion(storyId : UserStoryId, description : String, type : CriterionType) + + update(description : String, type : CriterionType) : void + } + } + + package "Value Objects" as DiscValueObjects { + class DiscoverySessionId <> + class UserStoryId <> + class AcceptanceCriterionId <> + class ProjectId <> + + enum SessionStatus <> { + DRAFT + PROCESSING + COMPLETED + FAILED + } + + enum StoryStatus <> { + DRAFT + APPROVED + REJECTED + } + + enum Priority <> { + LOW + MEDIUM + HIGH + CRITICAL + } + + enum CriterionType <> { + GIVEN_WHEN_THEN + CHECKLIST + } + } + + package "Exceptions" as DiscExceptions { + class DiscoverySessionNotFoundException <> + class SessionAlreadyProcessingException <> + class TranscriptRequiredException <> + class TokenQuotaExceededException <> + class UserStoryNotFoundException <> + class AcceptanceCriterionNotFoundException <> + class InvalidStoryTransitionException <> + } + } + } + + package "Api" as DiscApi { + class AiTokensConsumedEvent <> { + + organizationId : String + + tokensConsumed : Long + + occurredAt : Instant + -- + + {static} of(organizationId : String, tokensConsumed : Long) : AiTokensConsumedEvent + } + } + + package "Domain" { + package "Model" { + package "Events" as DiscDomainEvents { + class SessionProcessingStartedEvent <> { + + sessionId : String + + projectId : String + + occurredAt : Instant + -- + + {static} of(sessionId : String, projectId : String) : SessionProcessingStartedEvent + } + class UserStoriesGeneratedEvent <> { + + sessionId : String + + projectId : String + + storyCount : int + + occurredAt : Instant + -- + + {static} of(sessionId : String, projectId : String, storyCount : int) : UserStoriesGeneratedEvent + } + } + } + } +} + +' ── Inheritance ────────────────────────────────────────────────────── +DiscoverySession --|> AbstractAggregateRoot : extends +UserStory --|> AbstractAggregateRoot : extends + +DiscoverySessionNotFoundException --|> EntityNotFoundException +UserStoryNotFoundException --|> EntityNotFoundException +AcceptanceCriterionNotFoundException --|> EntityNotFoundException + +SessionAlreadyProcessingException --|> BusinessRuleViolationException +TranscriptRequiredException --|> BusinessRuleViolationException +TokenQuotaExceededException --|> BusinessRuleViolationException +InvalidStoryTransitionException --|> BusinessRuleViolationException + +' ── Dependencies ───────────────────────────────────────────────────── +DiscoverySession ..> SessionStatus : uses +UserStory ..> StoryStatus : uses +UserStory ..> Priority : uses +AcceptanceCriterion ..> CriterionType : uses + +' ── Cross-aggregate ID references ──────────────────────────────────── +DiscoverySession "1" o-- "1" ProjectId : projectId (reference) +UserStory "1" o-- "1" DiscoverySessionId : sessionId (reference) +UserStory "1" o-- "1" ProjectId : projectId (reference) + +' ── Domain Events published ─────────────────────────────────────────── +DiscoverySession ..> SessionProcessingStartedEvent : <> +DiscoverySession ..> UserStoriesGeneratedEvent : <> +DiscoverySession ..> AiTokensConsumedEvent : <> + +@enduml diff --git a/assets/diagrams/discovery/discovery-component.png b/assets/diagrams/discovery/discovery-component.png new file mode 100644 index 0000000..cfa6f67 Binary files /dev/null and b/assets/diagrams/discovery/discovery-component.png differ diff --git a/assets/diagrams/discovery/discovery-component.puml b/assets/diagrams/discovery/discovery-component.puml new file mode 100644 index 0000000..5e8d16f --- /dev/null +++ b/assets/diagrams/discovery/discovery-component.puml @@ -0,0 +1,62 @@ +@startuml discovery-component +!include ../c4/C4_Container.puml +!include ../c4/C4_Component.puml +!include ../styles/reqsai-c4-component-style.puml + +AddSystemTag("externalAI", $bgColor="#1B5E20", $fontColor="#FFFFFF", $borderColor="#0A3D10", $legendText="External AI / LLM Service") + +title Component Diagram -- Req Discovery Bounded Context (C4 Level 3) + +' ── External actors ────────────────────────────────────────────────── +Person(user, "Usuario", "Inicia sesiones de discovery\ny revisa user stories generadas.") +System_Ext(wsBC, "Workspace BC", "Verifica membresia y limites\ndel workspace vía Module API.", $tags="externalBC") +System_Ext(bilBC, "Billing BC", "Verifica limites de sesiones\ny tokens del plan.", $tags="externalBC") +System_Ext(aiSvc, "AI / LLM Service","OpenAI o similar: procesa\ntranscripciones y genera stories.", $tags="externalAI") +ContainerDb(postgres,"PostgreSQL", "Database", "discovery_sessions · user_stories", $tags="database") + +' ── Discovery Module boundary ──────────────────────────────────────── +Container_Boundary(disc, "Req Discovery [Spring Modulith Module]") { + + Component(sessionCtrl, "DiscoverySessionController", "REST Controller", "POST /sessions\nGET /sessions/{id}\nPATCH /sessions/{id}/start\nPATCH /sessions/{id}/complete\nDELETE /sessions/{id}", $tags="interface") + Component(storyCtrl, "UserStoryController", "REST Controller", "GET /projects/{id}/stories\nGET /stories/{id}\nPATCH /stories/{id}\nPATCH /stories/{id}/approve\nPATCH /stories/{id}/reject\nGET /stories/{id}/gherkin", $tags="interface") + + Component(sessionCmdHandlers, "Session Command Handlers", "CreateSessionCommandHandler\nStartSessionCommandHandler\nCompleteSessionCommandHandler\nCancelSessionCommandHandler", "Gestiona el ciclo de vida de\nlas sesiones de discovery.", $tags="application") + Component(storyCmdHandlers, "Story Command Handlers", "ApproveUserStoryCommandHandler\nRejectUserStoryCommandHandler\nUpdateUserStoryCommandHandler\nChangePriorityCommandHandler", "Gestiona el ciclo de revision\nde user stories.", $tags="application") + Component(queryHandlers, "Query Handlers", "GetSessionQueryHandler\nListSessionsQueryHandler\nGetUserStoryQueryHandler\nListStoriesByProjectQueryHandler\nGetGherkinQueryHandler", "Consultas de lectura.\n@Transactional(readOnly = true)", $tags="application") + Component(aiListener, "SessionCompletedEventListener", "@ApplicationModuleListener", "Llama al AI service para procesar\nla transcripcion y generar stories.", $tags="application") + + Component(domain, "Domain Model", "DiscoverySession · UserStory\nTranscriptContent · StoryContent\nSessionStatus · StoryStatus · Priority", "Logica de negocio pura: invariantes\ny reglas del discovery.", $tags="domain") + + Component(repos, "JPA Repositories", "DiscoverySessionRepository\nUserStoryRepository", "Implementan los repository ports.", $tags="infrastructure") + Component(adapters, "AI Adapter", "LlmTranscriptProcessingAdapter\nGherkinGenerationAdapter", "Implementan los AI service ports.\nIntegra con OpenAI API.", $tags="infrastructure") + Component(moduleApi,"DiscoveryModuleApiImpl", "Module API Facade", "Expone getSessionById()\ngetApprovedStoriesByProject()\na otros BCs (Gateway BC).", $tags="moduleApi") +} + +' ── External relations ─────────────────────────────────────────────── +Rel(user, sessionCtrl, "HTTP REST", "JSON / HTTPS") +Rel(user, storyCtrl, "HTTP REST", "JSON / HTTPS") +Rel(wsBC, moduleApi, "In-process", "Spring Bean call") +Rel(repos, postgres, "Reads / Writes", "JDBC / Hibernate ORM") +Rel(adapters, aiSvc, "AI Processing", "HTTPS / REST") + +' ── Internal relations ─────────────────────────────────────────────── +Rel(sessionCtrl, sessionCmdHandlers, "Delegates to") +Rel(sessionCtrl, queryHandlers, "Delegates to") +Rel(storyCtrl, storyCmdHandlers, "Delegates to") +Rel(storyCtrl, queryHandlers, "Delegates to") + +Rel(sessionCmdHandlers, domain, "Creates / mutates") +Rel(storyCmdHandlers, domain, "Mutates state") +Rel(queryHandlers, domain, "Reads") + +Rel(sessionCmdHandlers, repos, "Persists via port") +Rel(storyCmdHandlers, repos, "Persists via port") +Rel(queryHandlers, repos, "Reads via port") +Rel(moduleApi, repos, "Reads via port") + +Rel(domain, aiListener, "Publishes event", "Spring ApplicationEvent") +Rel(aiListener, adapters, "Processes transcript via port") +Rel(aiListener, repos, "Persists generated stories") + +SHOW_LEGEND() +@enduml diff --git a/assets/diagrams/discovery/discovery-database.png b/assets/diagrams/discovery/discovery-database.png new file mode 100644 index 0000000..149b60f Binary files /dev/null and b/assets/diagrams/discovery/discovery-database.png differ diff --git a/assets/diagrams/discovery/discovery-database.puml b/assets/diagrams/discovery/discovery-database.puml new file mode 100644 index 0000000..9f97308 --- /dev/null +++ b/assets/diagrams/discovery/discovery-database.puml @@ -0,0 +1,114 @@ +@startuml discovery-database +!include ../styles/reqsai-database-style.puml + +title Req Discovery BC -- Database Design Diagram + +legend top left + |= Clave |= Significado | + |<$REQSAI_BLUE_800> T | Tabla core (entidad) | + |<>| Primary Key | + |<>| Foreign Key | + |<>| NOT NULL | + |<>| UNIQUE | + |<>| INDEX | + |1:1| Relacion 1 a 1 | + |1:N| Relacion 1 a N (0..*) | +endlegend + +note as auditNote + Columnas de auditoria (todas las tablas) + - created_at TIMESTAMP <> + - updated_at TIMESTAMP <> + - created_by VARCHAR(36) + - updated_by VARCHAR(36) +end note + +' ── Table: discovery_sessions ──────────────────────────────────────── +CORE(discovery_sessions) { + id : VARCHAR(36) <> + -- + project_id : VARCHAR(36) <> + title : VARCHAR(255) <> + transcript : TEXT + status : VARCHAR(20) <> + processing_error : TEXT + -- +} + +note right of discovery_sessions + project_id + - Ref logica al Workspace BC + - Sin FK fisica (BCs aislados) + + status + - DRAFT | PROCESSING + - COMPLETED | FAILED + + transcript + - NULL hasta que se sube el archivo + - Texto plano extraido del audio/documento + + processing_error + - NULL si no hay error + - Mensaje de error del AI pipeline +end note + +' ── Table: user_stories ────────────────────────────────────────────── +CORE(user_stories) { + id : VARCHAR(36) <> + -- + session_id : VARCHAR(36) <> + project_id : VARCHAR(36) <> + title : VARCHAR(255) <> + role : VARCHAR(200) <> + action : VARCHAR(500) <> + benefit : VARCHAR(500) <> + priority : VARCHAR(20) <> + story_points : INT + status : VARCHAR(20) <> + -- +} + +note right of user_stories + session_id + - FK a discovery_sessions + + priority + - LOW | MEDIUM | HIGH | CRITICAL + + status + - DRAFT | APPROVED | REJECTED + + story_points + - NULL hasta que se asignen puntos + - Estimacion de esfuerzo (Fibonacci) +end note + +' ── Table: acceptance_criteria ─────────────────────────────────────── +CORE(acceptance_criteria) { + id : VARCHAR(36) <> + -- + story_id : VARCHAR(36) <> + description : TEXT <> + type : VARCHAR(30) <> + -- +} + +note right of acceptance_criteria + story_id + - FK a user_stories + + type + - GIVEN_WHEN_THEN + - CHECKLIST +end note + +' ── Relationships ──────────────────────────────────────────────────── +discovery_sessions ||--o{ user_stories : "1 sesion -> 0..* historias" +user_stories ||--o{ acceptance_criteria : "1 historia -> 0..* criterios" + +auditNote .. discovery_sessions +auditNote .. user_stories +auditNote .. acceptance_criteria + +@enduml diff --git a/assets/diagrams/gateway/gateway-class.png b/assets/diagrams/gateway/gateway-class.png new file mode 100644 index 0000000..d50c843 Binary files /dev/null and b/assets/diagrams/gateway/gateway-class.png differ diff --git a/assets/diagrams/gateway/gateway-class.puml b/assets/diagrams/gateway/gateway-class.puml new file mode 100644 index 0000000..92e5c48 --- /dev/null +++ b/assets/diagrams/gateway/gateway-class.puml @@ -0,0 +1,191 @@ +@startuml gateway-class +!include ../styles/reqsai-class-style.puml + +top to bottom direction + +title Gateway BC -- Tactical Class Diagram + +legend top right + |= Elemento |= Significado | + |<$REQSAI_NAVY_800> <> | Raiz de aggregate | + |<$REQSAI_GREEN_900> <> | Record inmutable y validado | + |<$REQSAI_VIOLET_900> <> | Enumeracion de dominio | + |<$REQSAI_BROWN_900> <> | Evento de dominio (inmutable) | + |<$REQSAI_GRAY_700> <> | Clase abstracta Shared Kernel | + |Relacion|Significado| + | --\\|> | extends (herencia) | + | ..> | uses / depends on | + | o-- | referencia por ID (cross-aggregate) | + | ..> <>| evento publicado por el aggregate | +endlegend + +package "Shared Kernel" as SharedKernel { + abstract class AbstractAggregateRoot <> + abstract class EntityNotFoundException <> + abstract class BusinessRuleViolationException <> +} + +package "Gateway" { + + package "Domain" { + package "Model" { + package "Aggregates" as GwAggregates { + + class Integration <> { + - id : IntegrationId + - projectId : ProjectId + - provider : IntegrationProvider + - status : IntegrationStatus + - config : IntegrationConfig + - encryptedToken : String + -- + # Integration() + + Integration(projectId : ProjectId, provider : IntegrationProvider, config : IntegrationConfig, encryptedToken : String) + ..Business Methods.. + + updateConfig(config : IntegrationConfig) : void + + rotateToken(encryptedToken : String) : void + + activate() : void + + deactivate() : void + + markError() : void + ..Query Methods.. + + isActive() : boolean + + hasError() : boolean + } + + class ExportRecord <> { + - id : ExportRecordId + - integrationId : IntegrationId + - storyId : UserStoryId + - externalIssueId : String + - externalUrl : String + - status : ExportStatus + - failureReason : String + -- + # ExportRecord() + + ExportRecord(integrationId : IntegrationId, storyId : UserStoryId) + ..Business Methods.. + + markExported(externalIssueId : String, externalUrl : String) : void + + markSynced() : void + + markFailed(failureReason : String) : void + + retry() : void + ..Query Methods.. + + isExported() : boolean + + isSynced() : boolean + + hasFailed() : boolean + } + } + + package "Value Objects" as GwValueObjects { + class IntegrationId <> + class ExportRecordId <> + class ProjectId <> + class UserStoryId <> + + class IntegrationConfig <> { + - baseUrl : String + - projectKey : String + - issueTypeMapping : Map + -- + + IntegrationConfig(baseUrl : String, projectKey : String, issueTypeMapping : Map) + + baseUrl() : String + + projectKey() : String + + issueTypeMapping() : Map + } + + enum IntegrationProvider <> { + JIRA + TRELLO + LINEAR + } + + enum IntegrationStatus <> { + ACTIVE + INACTIVE + ERROR + } + + enum ExportStatus <> { + PENDING + EXPORTED + SYNCED + FAILED + } + } + + package "Exceptions" as GwExceptions { + class IntegrationNotFoundException <> + class ExportRecordNotFoundException <> + class IntegrationAlreadyExistsException <> + class IntegrationInactiveException <> + class ExportFailedException <> + class ProviderAuthenticationException <> + } + } + } + + package "Api" as GwApi { + class StoryExportedEvent <> { + + exportRecordId : String + + storyId : String + + integrationId : String + + externalIssueId : String + + externalUrl : String + + occurredAt : Instant + -- + + {static} of(exportRecordId : String, storyId : String, integrationId : String, externalIssueId : String, externalUrl : String) : StoryExportedEvent + } + } + + package "Domain" { + package "Model" { + package "Events" as GwDomainEvents { + class ExportFailedEvent <> { + + exportRecordId : String + + storyId : String + + failureReason : String + + occurredAt : Instant + -- + + {static} of(exportRecordId : String, storyId : String, failureReason : String) : ExportFailedEvent + } + class IntegrationActivatedEvent <> { + + integrationId : String + + projectId : String + + provider : String + + occurredAt : Instant + -- + + {static} of(integrationId : String, projectId : String, provider : String) : IntegrationActivatedEvent + } + } + } + } +} + +' ── Inheritance ────────────────────────────────────────────────────── +Integration --|> AbstractAggregateRoot : extends +ExportRecord --|> AbstractAggregateRoot : extends + +IntegrationNotFoundException --|> EntityNotFoundException +ExportRecordNotFoundException --|> EntityNotFoundException + +IntegrationAlreadyExistsException --|> BusinessRuleViolationException +IntegrationInactiveException --|> BusinessRuleViolationException +ExportFailedException --|> BusinessRuleViolationException +ProviderAuthenticationException --|> BusinessRuleViolationException + +' ── Dependencies ───────────────────────────────────────────────────── +Integration ..> IntegrationProvider : uses +Integration ..> IntegrationStatus : uses +Integration ..> IntegrationConfig : uses +ExportRecord ..> ExportStatus : uses + +' ── Cross-aggregate ID references ──────────────────────────────────── +Integration "1" o-- "1" ProjectId : projectId (reference) +ExportRecord "1" o-- "1" IntegrationId : integrationId (reference) +ExportRecord "1" o-- "1" UserStoryId : storyId (reference) + +' ── Domain Events published ─────────────────────────────────────────── +Integration ..> IntegrationActivatedEvent : <> +ExportRecord ..> StoryExportedEvent : <> +ExportRecord ..> ExportFailedEvent : <> + +@enduml diff --git a/assets/diagrams/gateway/gateway-component.png b/assets/diagrams/gateway/gateway-component.png new file mode 100644 index 0000000..ed22e46 Binary files /dev/null and b/assets/diagrams/gateway/gateway-component.png differ diff --git a/assets/diagrams/gateway/gateway-component.puml b/assets/diagrams/gateway/gateway-component.puml new file mode 100644 index 0000000..b5aaa18 --- /dev/null +++ b/assets/diagrams/gateway/gateway-component.puml @@ -0,0 +1,61 @@ +@startuml gateway-component +!include ../c4/C4_Container.puml +!include ../c4/C4_Component.puml +!include ../styles/reqsai-c4-component-style.puml + +AddSystemTag("externalJira", $bgColor="#0052CC", $fontColor="#FFFFFF", $borderColor="#003893", $legendText="Jira Cloud (Atlassian)") + +title Component Diagram -- Gateway Bounded Context (C4 Level 3) + +' ── External actors ────────────────────────────────────────────────── +Person(user, "Usuario", "Conecta Jira y sincroniza\nuser stories aprobadas.") +System_Ext(discBC, "Req Discovery BC", "Publica UserStoryApprovedEvent\npara disparar sincronizacion.", $tags="externalBC") +System_Ext(wsBC, "Workspace BC", "Verifica membresia del workspace\nvía Module API.", $tags="externalBC") +System_Ext(jira, "Jira Cloud", "Atlassian Jira Cloud API v3:\ncreacion de issues via OAuth 2.0.", $tags="externalJira") +ContainerDb(postgres,"PostgreSQL", "Database", "jira_integrations · jira_sync_records", $tags="database") + +' ── Gateway Module boundary ────────────────────────────────────────── +Container_Boundary(gw, "Gateway [Spring Modulith Module]") { + + Component(integCtrl, "IntegrationController", "REST Controller", "POST /integrations/jira/connect\nGET /integrations/jira/status\nDELETE /integrations/jira\nGET /integrations/jira/callback", $tags="interface") + Component(syncCtrl, "SyncController", "REST Controller", "POST /sync/stories/{id}\nGET /sync/records\nPOST /sync/records/{id}/retry", $tags="interface") + + Component(integCmdHandlers, "Integration Command Handlers", "ConnectJiraCommandHandler\nDisconnectJiraCommandHandler\nRefreshJiraCredentialsCommandHandler", "Gestiona el ciclo de conexion\ncon Jira Cloud via OAuth 2.0.", $tags="application") + Component(syncCmdHandlers, "Sync Command Handlers", "SyncUserStoryCommandHandler\nRetrySyncCommandHandler", "Sincroniza user stories aprobadas\ncomo issues en Jira.", $tags="application") + Component(queryHandlers, "Query Handlers", "GetIntegrationStatusQueryHandler\nListSyncRecordsQueryHandler", "Consultas de estado de integracion\ny registros de sincronizacion.", $tags="application") + Component(storyListener, "UserStoryApprovedEventListener", "@ApplicationModuleListener", "Reacciona a stories aprobadas\ny dispara la sincronizacion con Jira.", $tags="application") + + Component(domain, "Domain Model", "JiraIntegration · JiraSyncRecord\nJiraCredentials\nIntegrationStatus · SyncStatus", "Logica de negocio pura: invariantes\ny reglas de integracion.", $tags="domain") + + Component(repos, "JPA Repositories", "JiraIntegrationRepository\nJiraSyncRecordRepository", "Implementan los repository ports.", $tags="infrastructure") + Component(adapters, "Jira Cloud Adapter","JiraCloudApiAdapter", "Implementa el Jira gateway port.\nOAuth 2.0 + Jira REST API v3.\nCreacion y consulta de issues.", $tags="infrastructure") +} + +' ── External relations ─────────────────────────────────────────────── +Rel(user, integCtrl, "HTTP REST", "JSON / HTTPS") +Rel(user, syncCtrl, "HTTP REST", "JSON / HTTPS") +Rel(repos, postgres, "Reads / Writes", "JDBC / Hibernate ORM") +Rel(adapters,jira, "Jira API", "HTTPS / OAuth 2.0") + +' ── Internal relations ─────────────────────────────────────────────── +Rel(integCtrl, integCmdHandlers, "Delegates to") +Rel(integCtrl, queryHandlers, "Delegates to") +Rel(syncCtrl, syncCmdHandlers, "Delegates to") +Rel(syncCtrl, queryHandlers, "Delegates to") + +Rel(integCmdHandlers, domain, "Creates / mutates") +Rel(syncCmdHandlers, domain, "Creates / mutates") +Rel(queryHandlers, domain, "Reads") + +Rel(integCmdHandlers, repos, "Persists via port") +Rel(integCmdHandlers, adapters, "OAuth exchange via port") +Rel(syncCmdHandlers, repos, "Persists via port") +Rel(syncCmdHandlers, adapters, "Creates Jira issue via port") +Rel(queryHandlers, repos, "Reads via port") + +Rel(discBC, storyListener, "Publishes event", "Spring ApplicationEvent") +Rel(storyListener,syncCmdHandlers,"Triggers sync") +Rel(wsBC, queryHandlers, "In-process", "Spring Bean call") + +SHOW_LEGEND() +@enduml diff --git a/assets/diagrams/gateway/gateway-database.png b/assets/diagrams/gateway/gateway-database.png new file mode 100644 index 0000000..62f69bf Binary files /dev/null and b/assets/diagrams/gateway/gateway-database.png differ diff --git a/assets/diagrams/gateway/gateway-database.puml b/assets/diagrams/gateway/gateway-database.puml new file mode 100644 index 0000000..fcd411e --- /dev/null +++ b/assets/diagrams/gateway/gateway-database.puml @@ -0,0 +1,104 @@ +@startuml gateway-database +!include ../styles/reqsai-database-style.puml + +title Gateway BC -- Database Design Diagram + +legend top left + |= Clave |= Significado | + |<$REQSAI_BLUE_800> T | Tabla core (entidad) | + |<>| Primary Key | + |<>| Foreign Key | + |<>| NOT NULL | + |<>| UNIQUE | + |<>| INDEX | + |1:1| Relacion 1 a 1 | + |1:N| Relacion 1 a N (0..*) | +endlegend + +note as auditNote + Columnas de auditoria (todas las tablas) + - created_at TIMESTAMP <> + - updated_at TIMESTAMP <> + - created_by VARCHAR(36) + - updated_by VARCHAR(36) +end note + +' ── Table: integrations ────────────────────────────────────────────── +CORE(integrations) { + id : VARCHAR(36) <> + -- + project_id : VARCHAR(36) <> + provider : VARCHAR(20) <> + status : VARCHAR(20) <> + -- + base_url : VARCHAR(500) <> + project_key : VARCHAR(100) <> + issue_type_mapping : TEXT <> + -- + encrypted_token : TEXT <> + -- +} + +note right of integrations + project_id + - Ref logica al Workspace BC + - Sin FK fisica (BCs aislados) + + provider + - JIRA | TRELLO | LINEAR + + status + - ACTIVE | INACTIVE | ERROR + + Columnas base_url / project_key + / issue_type_mapping + - Embedding del VO IntegrationConfig + - issue_type_mapping: JSON object (TEXT) + + encrypted_token + - Token cifrado en reposo (AES-256) + - Credencial de acceso al proveedor externo +end note + +' ── Table: export_records ──────────────────────────────────────────── +CORE(export_records) { + id : VARCHAR(36) <> + -- + integration_id : VARCHAR(36) <> + story_id : VARCHAR(36) <> + status : VARCHAR(20) <> + -- + external_issue_id : VARCHAR(100) + external_url : VARCHAR(500) + failure_reason : TEXT + -- +} + +note right of export_records + integration_id + - FK a integrations + + story_id + - Ref logica al Discovery BC + - Sin FK fisica (BCs aislados) + + status + - PENDING | EXPORTED + - SYNCED | FAILED + + external_issue_id + - Ej. Jira: PROJ-123 + - Ej. Linear: ENG-456 + - NULL si aun no exportado + + failure_reason + - NULL si no hay error +end note + +' ── Relationships ──────────────────────────────────────────────────── +integrations ||--o{ export_records : "1 integracion -> 0..* exportaciones" + +auditNote .. integrations +auditNote .. export_records + +@enduml diff --git a/assets/diagrams/iam/iam-class.png b/assets/diagrams/iam/iam-class.png new file mode 100644 index 0000000..6cc2632 Binary files /dev/null and b/assets/diagrams/iam/iam-class.png differ diff --git a/assets/diagrams/iam/iam-class.puml b/assets/diagrams/iam/iam-class.puml new file mode 100644 index 0000000..29d3996 --- /dev/null +++ b/assets/diagrams/iam/iam-class.puml @@ -0,0 +1,226 @@ +@startuml iam-class +!include ../styles/reqsai-class-style.puml + +top to bottom direction + +title IAM Bounded Context -- Tactical Class Diagram +' ── Legend ───────────────────────────────────────────────────────── +legend top right + |= Elemento |= Significado | + |<$REQSAI_NAVY_800> <> | Raiz de aggregate | + |<$REQSAI_GREEN_900> <> | Record inmutable y validado | + |<$REQSAI_VIOLET_900> <> | Enumeracion de dominio | + |<$REQSAI_BROWN_900> <> | Evento de dominio (inmutable) | + |<$REQSAI_GRAY_700> <> | Clase abstracta Shared Kernel | + |Relacion|Significado| + | --\\|> | extends (herencia) | + | ..> | uses / depends on | + | o-- | referencia por ID (cross-aggregate) | + | ..> <>| evento publicado por el aggregate | +endlegend + +package "Shared Kernel" as SharedKernel { + abstract class AbstractAggregateRoot <> { + # createdAt : Instant + # updatedAt : Instant + # createdBy : String + # updatedBy : String + -- + + getIdValue() : String + + isNew() : boolean + } + + abstract class EntityNotFoundException <> + abstract class BusinessRuleViolationException <> + abstract class AuthenticationException <> +} + +package "IAM" { + + package "Domain" { + package "Model" { + package "Aggregates" as DomainAggregates { + class Account <> { + - id : AccountId + - email : Email + - passwordHash : String + - status : AccountStatus + - verificationCode : String + - verificationCodeExpiresAt : Instant + -- + # Account() + + Account(email : Email, passwordHash : String) + ..Business Methods.. + + verifyEmail(code : String, now : Instant) : void + + changePassword(newPasswordHash : String) : void + + suspend() : void + + activate() : void + + delete() : void + + generateVerificationCode(code : String, expiresAt : Instant) : void + ..Query Methods.. + + isPendingVerification() : boolean + + isActive() : boolean + + isSuspended() : boolean + + isDeleted() : boolean + } + + class User <> { + - id : UserId + - accountId : AccountId + - firstName : String + - lastName : String + - avatarUrl : String + - preferences : UserPreferences + -- + # User() + + User(accountId : AccountId, firstName : String, lastName : String) + ..Business Methods.. + + updateProfile(firstName : String, lastName : String, avatarUrl : String) : void + + updatePreferences(preferences : UserPreferences) : void + ..Query Methods.. + + getFullName() : String + } + + class RefreshToken <> { + - id : RefreshTokenId + - tokenHash : String + - userId : UserId + - status : TokenStatus + - expiresAt : Instant + - revokedAt : Instant + -- + # RefreshToken() + + RefreshToken(tokenHash : String, userId : UserId, expiresAt : Instant) + ..Business Methods.. + + revoke(revokedAt : Instant) : void + + rotate() : void + ..Query Methods.. + + isValid(now : Instant) : boolean + + isExpired(now : Instant) : boolean + } + } + } + } + + package "Domain" { + package "Model" { + package "Value Objects" as DomainValueObjects { + class AccountId <> + class UserId <> + class RefreshTokenId <> + + class Email <> { + - value : String + -- + + Email(value : String) + + {static} of(value : String) : Email + + value() : String + } + + class UserPreferences <> { + - lastVisitedOrgId : String + - lastVisitedProjectId : String + -- + + UserPreferences(lastVisitedOrgId : String, lastVisitedProjectId : String) + + lastVisitedOrgId() : String + + lastVisitedProjectId() : String + } + + enum AccountStatus <> { + PENDING_VERIFICATION + ACTIVE + SUSPENDED + DELETED + } + + enum TokenStatus <> { + ACTIVE + REVOKED + EXPIRED + } + } + } + } + + package "Api" as ApiEvents { + class AccountCreatedEvent <> { + + accountId : String + + userId : String + + email : String + + occurredAt : Instant + -- + + {static} of(accountId : String, userId : String, email : String) : AccountCreatedEvent + } + + class EmailVerificationRequestedEvent <> { + + email : String + + verificationCode : String + + expirationMinutes : int + + occurredAt : Instant + -- + + {static} of(email : String, code : String, minutes : int) : EmailVerificationRequestedEvent + } +} + + package "Domain" { + package "Model" { + package "Events" as DomainEvents { + class AccountVerifiedEvent <> { + + accountId : String + + occurredAt : Instant + -- + + {static} of(accountId : String) : AccountVerifiedEvent + } +} + + package "Domain" { + package "Model" { + package "Exceptions" as DomainExceptions { + class AccountNotFoundException <> + class AccountAlreadyExistsException <> + class InvalidCredentialsException <> + class AccountNotVerifiedException <> + class CannotSuspendAccountException <> + class InvalidRefreshTokenException <> + class InvalidVerificationCodeException <> + class UserNotFoundException <> + } + } + } + +} + + +' ── Inheritance ──────────────────────────────────────────────────── +Account --|> AbstractAggregateRoot : extends +User --|> AbstractAggregateRoot : extends +RefreshToken --|> AbstractAggregateRoot : extends + +AccountNotFoundException --|> EntityNotFoundException +UserNotFoundException --|> EntityNotFoundException + +AccountAlreadyExistsException --|> BusinessRuleViolationException +AccountNotVerifiedException --|> BusinessRuleViolationException +CannotSuspendAccountException --|> BusinessRuleViolationException +InvalidVerificationCodeException --|> BusinessRuleViolationException + +InvalidCredentialsException --|> AuthenticationException +InvalidRefreshTokenException --|> AuthenticationException + +' ── Dependencies ─────────────────────────────────────────────────── +Account ..> Email : uses +Account ..> AccountStatus : uses +User ..> UserPreferences : uses +RefreshToken ..> TokenStatus : uses + +' ── Cross-aggregate ID references ────────────────────────────────── +User "1" o-- "1" AccountId : accountId (reference) +RefreshToken "1" o-- "1" UserId : userId (reference) + +' ── Domain Events published ──────────────────────────────────────── +Account ..> AccountCreatedEvent : <> +Account ..> EmailVerificationRequestedEvent : <> +Account ..> AccountVerifiedEvent : <> + + +@enduml diff --git a/assets/diagrams/iam/iam-component.png b/assets/diagrams/iam/iam-component.png new file mode 100644 index 0000000..168aa09 Binary files /dev/null and b/assets/diagrams/iam/iam-component.png differ diff --git a/assets/diagrams/iam/iam-component.puml b/assets/diagrams/iam/iam-component.puml new file mode 100644 index 0000000..a9b56c6 --- /dev/null +++ b/assets/diagrams/iam/iam-component.puml @@ -0,0 +1,63 @@ +@startuml iam-component +!include ../c4/C4_Container.puml +!include ../c4/C4_Component.puml + +!include ../styles/reqsai-c4-component-style.puml + +title Component Diagram — IAM Bounded Context (C4 Level 3) + +' ── External actors ──────────────────────────────────────────────── +Person(user, "Usuario", "Registra cuenta, inicia sesión\ny gestiona su perfil.") +System_Ext(wsBC, "Workspace BC", "Consulta identidad del usuario\nautenticado vía Module API.", $tags="externalBC") +System_Ext(smtp, "SMTP Service", "Envía correos de verificación\ny notificaciones de cuenta.", $tags="externalMail") +ContainerDb(postgres, "PostgreSQL", "Database", "accounts · users · refresh_tokens", $tags="database") + +' ── IAM Module boundary ───────────────────────────────────────────── +Container_Boundary(iam, "IAM [Spring Modulith Module]") { + + Component(authCtrl, "AuthenticationController", "REST Controller", "POST /authentication/sign-up\nPOST /authentication/sign-in\nPOST /authentication/verify\nPOST /authentication/refresh\nPOST /authentication/sign-out", $tags="interface") + Component(userCtrl, "UserController", "REST Controller", "GET /users/me\nPATCH /users/me/profile\nPATCH /users/me/preferences", $tags="interface") + + Component(authHandlers, "Auth Command Handlers", "SignUpCommandHandler · SignInCommandHandler\nVerifyEmailCommandHandler · ResendVerificationCodeCommandHandler", "Gestiona registro, autenticación\ny verificación de correo.", $tags="application") + Component(sessionHandlers,"Session Command Handlers", "RefreshSessionCommandHandler\nRevokeRefreshTokenCommandHandler", "Rota y revoca refresh tokens\npara gestión segura de sesión.", $tags="application") + Component(userHandlers, "User Command Handlers", "UpdateUserProfileCommandHandler\nUpdateUserPreferencesCommandHandler", "Actualiza perfil y preferencias\ndel usuario autenticado.", $tags="application") + Component(queryHandlers, "Query Handlers", "GetAuthenticatedUserQueryHandler\nGetUserByIdQueryHandler", "Consultas de lectura del perfil.\n@Transactional(readOnly = true)", $tags="application") + Component(eventListener, "EmailVerificationRequestedEventListener", "@ApplicationModuleListener", "Reacciona al evento de verificación\ny delega el envío de correo al adapter.", $tags="application") + + Component(domain, "Domain Model", "Account · User · RefreshToken\nEmail · UserPreferences\nAccountStatus · TokenStatus", "Lógica de negocio pura: invariantes,\nreglas de dominio y eventos de dominio.", $tags="domain") + + Component(repos, "JPA Repositories", "AccountRepository · UserRepository\nRefreshTokenRepository", "Implementan los repository ports.\nAcceso directo JPA sobre aggregates.", $tags="infrastructure") + Component(adapters, "Service Adapters", "JwtTokenServiceAdapter · BCryptHashingServiceAdapter\nOtpVerificationServiceAdapter · SmtpEmailNotificationAdapter", "Implementan los service ports.\nJJWT · BCrypt · SecureRandom · Spring Mail", $tags="infrastructure") + Component(security, "Security Configuration", "WebSecurityConfiguration\nBearerAuthorizationRequestFilter", "Spring Security stateless con JWT.\nFiltra y autentica cada request entrante.", $tags="security") + Component(moduleApi, "IamModuleApiImpl", "Module API Facade", "Única interfaz expuesta a otros BCs.\nProvee identidad del usuario autenticado.", $tags="moduleApi") +} + +' ── External relations ────────────────────────────────────────────── +Rel(user, authCtrl, "HTTP REST", "JSON / HTTPS") +Rel(user, userCtrl, "HTTP REST", "JSON / HTTPS") +Rel(wsBC, moduleApi, "In-process", "Spring Bean call") +Rel(repos, postgres, "Reads / Writes","JDBC / Hibernate ORM") +Rel(adapters, smtp, "Sends emails", "SMTP / TLS") + +' ── Internal relations ────────────────────────────────────────────── +Rel(authCtrl, authHandlers, "Delegates to") +Rel(authCtrl, sessionHandlers, "Delegates to") +Rel(userCtrl, userHandlers, "Delegates to") +Rel(userCtrl, queryHandlers, "Delegates to") + +Rel(authHandlers, domain, "Creates / mutates") +Rel(sessionHandlers, domain, "Mutates state") +Rel(userHandlers, domain, "Mutates state") +Rel(queryHandlers, domain, "Reads") + +Rel(authHandlers, repos, "Persists via port") +Rel(authHandlers, adapters, "Hashes / generates OTP via port") +Rel(sessionHandlers, repos, "Persists via port") +Rel(userHandlers, repos, "Persists via port") +Rel(queryHandlers, repos, "Reads via port") + +Rel(domain, eventListener, "Publishes event", "Spring ApplicationEvent") +Rel(eventListener, adapters, "Sends email via port") + +SHOW_LEGEND() +@enduml diff --git a/assets/diagrams/iam/iam-database.png b/assets/diagrams/iam/iam-database.png new file mode 100644 index 0000000..38ec978 Binary files /dev/null and b/assets/diagrams/iam/iam-database.png differ diff --git a/assets/diagrams/iam/iam-database.puml b/assets/diagrams/iam/iam-database.puml new file mode 100644 index 0000000..03aeb7f --- /dev/null +++ b/assets/diagrams/iam/iam-database.puml @@ -0,0 +1,101 @@ +@startuml iam-database +!include ../styles/reqsai-database-style.puml + +title IAM Bounded Context -- Database Design Diagram + +' ── Legend ───────────────────────────────────────────────────────── +legend top left + |= Clave |= Significado | + |<$REQSAI_BLUE_800> T | Tabla core (entidad) | + |<$REQSAI_TEAL_800> S | Tabla sesion / token | + |<>| Primary Key | + |<>| Foreign Key | + |<>| NOT NULL | + |<>| UNIQUE | + |<>| INDEX | + |1:1| Relacion 1 a 1 (obligatoria) | + |1:N| Relacion 1 a N (0..*) | +endlegend + +' ── Audit columns note ───────────────────────────────────────────── +note as auditNote + Columnas de auditoria (todas las tablas) + - created_at TIMESTAMP <> + - updated_at TIMESTAMP <> + - created_by VARCHAR(36) + - updated_by VARCHAR(36) +end note + +' ── Table: accounts ──────────────────────────────────────────────── +CORE(accounts) { + id : VARCHAR(36) <> + -- + email_value : VARCHAR(255) <> + password_hash : VARCHAR(255) <> + status : VARCHAR(30) <> + verification_code : VARCHAR(10) + verification_code_expires_at : TIMESTAMP + -- +} + +note right of accounts + email_value + - Columna embebida del Value Object Email + - Indexada para sign-in y unicidad + + status + - PENDING_VERIFICATION | ACTIVE + - SUSPENDED | DELETED +end note + +' ── Table: users ─────────────────────────────────────────────────── +CORE(users) { + id : VARCHAR(36) <> + -- + account_id : VARCHAR(36) <> + first_name : VARCHAR(100) <> + last_name : VARCHAR(100) <> + avatar_url : VARCHAR(500) + last_visited_org_id : VARCHAR(36) + last_visited_project_id : VARCHAR(36) + -- +} + +note right of users + last_visited_org_id y last_visited_project_id + - Columnas embebidas del VO UserPreferences + + account_id + - Restriccion UQ garantiza relacion 1:1 con accounts +end note + +' ── Table: refresh_tokens ────────────────────────────────────────── +SESSION(refresh_tokens) { + id : VARCHAR(36) <> + -- + token_hash : VARCHAR(255) <> + user_id : VARCHAR(36) <> + status : VARCHAR(20) <> + expires_at : TIMESTAMP <> + revoked_at : TIMESTAMP + -- +} + +note right of refresh_tokens + token_hash + - Hash SHA-256 del token en texto plano + - Indexado para validacion y rotacion de sesion + + status + - ACTIVE | REVOKED | EXPIRED +end note + +' ── Relationships ─────────────────────────────────────────────────── +accounts ||--|| users : "1 cuenta -> 1 perfil" +users ||--o{ refresh_tokens : "1 usuario -> 0..* tokens" + +auditNote .. accounts +auditNote .. users +auditNote .. refresh_tokens + +@enduml diff --git a/assets/diagrams/styles/reqsai-c4-component-style.puml b/assets/diagrams/styles/reqsai-c4-component-style.puml new file mode 100644 index 0000000..a186973 --- /dev/null +++ b/assets/diagrams/styles/reqsai-c4-component-style.puml @@ -0,0 +1,38 @@ +'' ReqsAI standard styling for C4 Component (Level 3) diagrams. +'' Must be included AFTER C4_Container.puml and C4_Component.puml. + +!include reqsai-palette.puml + +skinparam shadowing false +skinparam defaultFontName Segoe UI +skinparam Padding 4 +skinparam wrapWidth 220 +skinparam nodesep 40 +skinparam ranksep 60 + +'' Fixes "missing glyph" squares in some environments by avoiding special legend glyphs. +'' (C4 legend marker is patched to ASCII in our local C4.puml, but keep this as a safe default.) +skinparam LegendFontName Segoe UI + +'' Standard: no icons/sprites anywhere (including Person) +HIDE_PERSON_SPRITE() + +'' Component layer tags +AddComponentTag("interface", $bgColor=$REQSAI_BLUE_800, $fontColor="#FFFFFF", $borderColor=$REQSAI_BLUE_900, $legendText="Interface Layer (REST Controllers)") + +AddComponentTag("application", $bgColor=$REQSAI_BLUE_700, $fontColor="#FFFFFF", $borderColor=$REQSAI_BLUE_800, $legendText="Application Layer (CQRS Handlers)") + +AddComponentTag("domain", $bgColor=$REQSAI_BLUE_900, $fontColor="#FFFFFF", $borderColor="#082A6B", $legendText="Domain Layer (DDD Aggregates + VOs)") + +AddComponentTag("infrastructure", $bgColor=$REQSAI_LIGHTBLUE_700, $fontColor="#FFFFFF", $borderColor=$REQSAI_LIGHTBLUE_800, $legendText="Infrastructure Layer (JPA + Adapters)") + +AddComponentTag("security", $bgColor=$REQSAI_ORANGE_900, $fontColor="#FFFFFF", $borderColor=$REQSAI_ORANGE_950, $legendText="Security (Spring Security + JWT Filter)") + +AddComponentTag("moduleApi", $bgColor=$REQSAI_PURPLE_700, $fontColor="#FFFFFF", $borderColor=$REQSAI_PURPLE_900, $legendText="Module API Facade (inter-BC contract)") + +'' External / infrastructure tags +AddSystemTag("externalBC", $bgColor="#455A64", $fontColor="#FFFFFF", $borderColor="#263238", $legendText="Other Bounded Context") + +AddSystemTag("externalMail", $bgColor="#546E7A", $fontColor="#FFFFFF", $borderColor="#37474F", $legendText="External SMTP Service") + +AddContainerTag("database", $bgColor=$REQSAI_TEAL_800, $fontColor="#FFFFFF", $borderColor=$REQSAI_TEAL_900, $legendText="Relational Database (PostgreSQL)") diff --git a/assets/diagrams/styles/reqsai-class-style.puml b/assets/diagrams/styles/reqsai-class-style.puml new file mode 100644 index 0000000..2c62373 --- /dev/null +++ b/assets/diagrams/styles/reqsai-class-style.puml @@ -0,0 +1,108 @@ +'' ReqsAI standard styling for UML class diagrams. + +!include reqsai-palette.puml + +skinparam shadowing false +skinparam defaultFontName Segoe UI +skinparam defaultFontSize 11 +skinparam classAttributeIconSize 0 +skinparam roundcorner 6 +skinparam ArrowColor $REQSAI_ARROW +skinparam ArrowThickness 1.3 + +skinparam class { + BackgroundColor $REQSAI_NEUTRAL_50 + BorderColor $REQSAI_NEUTRAL_600 + FontColor $REQSAI_NEUTRAL_800 + FontSize 11 + AttributeFontSize 10 +} + +'' DDD stereotypes +skinparam class<> { + ' Keep it consistent with the rest: light fill, colored header. + HeaderBackgroundColor $REQSAI_NAVY_800 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_NAVY_800 + BackgroundColor #F8FAFC +} + +skinparam class<> { + HeaderBackgroundColor $REQSAI_GREEN_900 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_GREEN_900 + BackgroundColor #F0FDF4 +} + +skinparam class<> { + HeaderBackgroundColor $REQSAI_VIOLET_900 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_VIOLET_900 + BackgroundColor #FAF5FF +} + +skinparam class<> { + HeaderBackgroundColor $REQSAI_BROWN_900 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_BROWN_900 + BackgroundColor #FFF7F4 +} + +skinparam class<> { + HeaderBackgroundColor $REQSAI_GRAY_700 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_GRAY_700 + BackgroundColor #F9FAFB +} + +'' Architectural stereotypes (hexagonal + layered) +skinparam class<> { + HeaderBackgroundColor $REQSAI_BLUE_800 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_BLUE_800 + BackgroundColor #EFF6FF +} + +skinparam class<> { + HeaderBackgroundColor $REQSAI_TEAL_900 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_TEAL_900 + BackgroundColor #ECFDF5 +} + +skinparam class<> { + HeaderBackgroundColor $REQSAI_ORANGE_900 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_ORANGE_900 + BackgroundColor #FFF7ED +} + +skinparam class<> { + HeaderBackgroundColor $REQSAI_PURPLE_700 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_PURPLE_700 + BackgroundColor #FAF5FF +} + +skinparam class<> { + HeaderBackgroundColor $REQSAI_LIGHTBLUE_800 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_LIGHTBLUE_800 + BackgroundColor #F0F9FF +} + +skinparam class<> { + HeaderBackgroundColor $REQSAI_GRAY_700 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_GRAY_700 + BackgroundColor #F8FAFC +} + +skinparam class<> { + HeaderBackgroundColor $REQSAI_NEUTRAL_900 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_NEUTRAL_900 + BackgroundColor #F1F5F9 +} + +hide empty members diff --git a/assets/diagrams/styles/reqsai-database-style.puml b/assets/diagrams/styles/reqsai-database-style.puml new file mode 100644 index 0000000..813da57 --- /dev/null +++ b/assets/diagrams/styles/reqsai-database-style.puml @@ -0,0 +1,43 @@ +'' ReqsAI standard styling for database design diagrams. + +!include reqsai-palette.puml + +skinparam shadowing false +skinparam defaultFontName Segoe UI +skinparam defaultFontSize 11 +skinparam linetype ortho +skinparam roundcorner 4 + +hide circle +hide empty methods + +skinparam entity { + BackgroundColor $REQSAI_NEUTRAL_50 + BorderColor $REQSAI_NEUTRAL_600 + FontColor $REQSAI_NEUTRAL_800 + FontSize 11 +} + +'' Table categories +skinparam entity<> { + HeaderBackgroundColor $REQSAI_BLUE_800 + HeaderFontColor #FFFFFF + BorderColor $REQSAI_BLUE_800 +} + +skinparam entity<> { + HeaderBackgroundColor $REQSAI_TEAL_800 + HeaderFontColor #FFFFFF + BorderColor #007A68 + BackgroundColor #F0FDFA +} + +skinparam note { + BackgroundColor $REQSAI_NOTE_BG + BorderColor $REQSAI_NOTE_BORDER + FontSize 10 +} + +'' Table macros +!define CORE(x) entity x << (T,$REQSAI_BLUE_800) >> +!define SESSION(x) entity x << (S,$REQSAI_TEAL_800) >> diff --git a/assets/diagrams/styles/reqsai-palette.puml b/assets/diagrams/styles/reqsai-palette.puml new file mode 100644 index 0000000..4adbb53 --- /dev/null +++ b/assets/diagrams/styles/reqsai-palette.puml @@ -0,0 +1,38 @@ +'' ReqsAI Diagram Palette (ASCII-only) +'' Shared color tokens to keep diagrams visually consistent. + +!$REQSAI_NEUTRAL_50 = "#F8FAFC" +!$REQSAI_NEUTRAL_100 = "#F1F5F9" +!$REQSAI_NEUTRAL_200 = "#E2E8F0" +!$REQSAI_NEUTRAL_600 = "#475569" +!$REQSAI_NEUTRAL_800 = "#1E293B" +!$REQSAI_NEUTRAL_900 = "#0F172A" + +!$REQSAI_NAVY_800 = "#274C77" + +!$REQSAI_BLUE_900 = "#0D47A1" +!$REQSAI_BLUE_800 = "#1565C0" +!$REQSAI_BLUE_700 = "#1976D2" +!$REQSAI_BLUE_600 = "#1E88E5" + +!$REQSAI_LIGHTBLUE_700 = "#0288D1" +!$REQSAI_LIGHTBLUE_800 = "#0277BD" + +!$REQSAI_TEAL_800 = "#00695C" +!$REQSAI_TEAL_900 = "#004D40" + +!$REQSAI_PURPLE_700 = "#4527A0" +!$REQSAI_PURPLE_900 = "#2E1870" + +!$REQSAI_ORANGE_900 = "#BF360C" +!$REQSAI_ORANGE_950 = "#7F2407" + +!$REQSAI_VIOLET_900 = "#4C1D95" +!$REQSAI_GREEN_900 = "#14532D" +!$REQSAI_BROWN_900 = "#7C2D12" +!$REQSAI_GRAY_700 = "#374151" + +!$REQSAI_NOTE_BG = "#FEF9C3" +!$REQSAI_NOTE_BORDER = "#CA8A04" + +!$REQSAI_ARROW = "#374151" diff --git a/assets/diagrams/workspace/workspace-class.png b/assets/diagrams/workspace/workspace-class.png new file mode 100644 index 0000000..ccb16df Binary files /dev/null and b/assets/diagrams/workspace/workspace-class.png differ diff --git a/assets/diagrams/workspace/workspace-class.puml b/assets/diagrams/workspace/workspace-class.puml new file mode 100644 index 0000000..e77396f --- /dev/null +++ b/assets/diagrams/workspace/workspace-class.puml @@ -0,0 +1,346 @@ +@startuml workspace-class +!include ../styles/reqsai-class-style.puml + +top to bottom direction + +title Workspace BC -- Tactical Class Diagram + +legend top right + |= Elemento |= Significado | + |<$REQSAI_NAVY_800> <> | Raiz de aggregate | + |<$REQSAI_GREEN_900> <> | Record inmutable y validado | + |<$REQSAI_VIOLET_900> <> | Enumeracion de dominio | + |<$REQSAI_BROWN_900> <> | Evento de dominio (inmutable) | + |<$REQSAI_GRAY_700> <> | Clase abstracta Shared Kernel | + |Relacion|Significado| + | --\\|> | extends (herencia) | + | ..> | uses / depends on | + | o-- | referencia por ID (cross-aggregate) | + | ..> <>| evento publicado por el aggregate | +endlegend + +package "Shared Kernel" as SharedKernel { + abstract class AbstractAggregateRoot <> + abstract class EntityNotFoundException <> + abstract class BusinessRuleViolationException <> +} + +package "Workspace" { + + package "Domain" { + package "Model" { + package "Aggregates" as WsAggregates { + + class Organization <> { + - id : OrganizationId + - name : String + - slug : String + - ownerId : UserId + - status : OrgStatus + - planLimits : PlanLimits + -- + # Organization() + + Organization(name : String, slug : String, ownerId : UserId) + ..Business Methods.. + + rename(name : String) : void + + updateLimits(limits : PlanLimits) : void + + deactivate() : void + + reactivate() : void + + delete() : void + } + + class Member <> { + - id : MemberId + - organizationId : OrganizationId + - userId : UserId + - role : OrgRole + - status : MemberStatus + - invitedBy : UserId + - invitedAt : Instant + -- + # Member() + + Member(organizationId : OrganizationId, userId : UserId, role : OrgRole) + ..Business Methods.. + + changeRole(role : OrgRole) : void + + accept() : void + + deactivate() : void + + reactivate() : void + } + + class Project <> { + - id : ProjectId + - organizationId : OrganizationId + - name : String + - description : String + - technicalProfile : TechnicalProfile + - status : ProjectStatus + - descriptionEmbedding : List + - constraints : List + -- + # Project() + + Project(organizationId : OrganizationId, name : String, description : String, technicalProfile : TechnicalProfile) + ..Business Methods.. + + updateDetails(name : String, description : String, technicalProfile : TechnicalProfile) : void + + updateEmbedding(embedding : List) : void + + addConstraint(description : String) : void + + removeConstraint(constraintId : ProjectConstraintId) : void + + archive() : void + + restore() : void + } + + class ProjectRole <> { + - id : ProjectRoleId + - projectId : ProjectId + - name : String + - permissions : Set + -- + # ProjectRole() + + ProjectRole(projectId : ProjectId, name : String, permissions : Set) + ..Business Methods.. + + rename(name : String) : void + + updatePermissions(permissions : Set) : void + } + + class ProjectMember <> { + - id : ProjectMemberId + - projectId : ProjectId + - memberId : MemberId + - roleId : ProjectRoleId + - assignedBy : UserId + - assignedAt : Instant + -- + # ProjectMember() + + ProjectMember(projectId : ProjectId, memberId : MemberId, roleId : ProjectRoleId, assignedBy : UserId, assignedAt : Instant) + ..Business Methods.. + + changeRole(roleId : ProjectRoleId) : void + } + + class ProjectDocument <> { + - id : ProjectDocumentId + - projectId : ProjectId + - name : String + - url : String + - mimeType : String + - status : DocumentStatus + -- + # ProjectDocument() + + ProjectDocument(projectId : ProjectId, name : String, url : String, mimeType : String) + ..Business Methods.. + + archive() : void + + restore() : void + } + + class Glossary <> { + - id : GlossaryId + - projectId : ProjectId + - terms : List + -- + # Glossary() + + Glossary(projectId : ProjectId) + ..Business Methods.. + + addTerm(term : String, definition : String, synonyms : List, addedBy : UserId) : void + + removeTerm(termId : GlossaryTermId) : void + + updateTerm(termId : GlossaryTermId, definition : String, synonyms : List) : void + } + } + } + } + + package "Domain" { + package "Model" { + package "Value Objects" as WsValueObjects { + class OrganizationId <> + class MemberId <> + class ProjectId <> + class ProjectRoleId <> + class ProjectMemberId <> + class ProjectDocumentId <> + class GlossaryId <> + class GlossaryTermId <> + class ProjectConstraintId <> + class UserId <> + + class PlanLimits <> { + - maxMembers : int + - maxProjects : int + - maxDocumentsPerProject : int + - maxTokensPerMonth : long + - maxGlossaryTermsPerProject : int + -- + + PlanLimits(maxMembers : int, maxProjects : int, maxDocumentsPerProject : int, maxTokensPerMonth : long, maxGlossaryTermsPerProject : int) + } + + class TechnicalProfile <> { + - programmingLanguages : List + - frameworks : List + - architecture : String + - domain : String + -- + + TechnicalProfile(programmingLanguages : List, frameworks : List, architecture : String, domain : String) + } + + enum OrgStatus <> { + ACTIVE + INACTIVE + DELETED + } + + enum OrgRole <> { + OWNER + ADMIN + MEMBER + } + + enum MemberStatus <> { + ACTIVE + PENDING + INACTIVE + } + + enum ProjectStatus <> { + ACTIVE + ARCHIVED + } + + enum DocumentStatus <> { + ACTIVE + ARCHIVED + } + + enum Permission <> { + READ_PROJECT + WRITE_PROJECT + DELETE_PROJECT + MANAGE_MEMBERS + MANAGE_ROLES + UPLOAD_DOCUMENTS + MANAGE_GLOSSARY + RUN_DISCOVERY + MANAGE_INTEGRATIONS + } + } + + package "Exceptions" as WsExceptions { + class OrganizationNotFoundException <> + class OrganizationSlugAlreadyExistsException <> + class MemberNotFoundException <> + class MemberAlreadyExistsException <> + class MemberPlanLimitExceededException <> + class InsufficientPermissionsException <> + class ProjectNotFoundException <> + class ProjectNameAlreadyExistsException <> + class ProjectPlanLimitExceededException <> + class ProjectRoleNotFoundException <> + class ProjectRoleNameAlreadyExistsException <> + class ProjectMemberNotFoundException <> + class ProjectMemberAlreadyExistsException <> + class ProjectDocumentNotFoundException <> + class DocumentPlanLimitExceededException <> + class GlossaryNotFoundException <> + class GlossaryTermNotFoundException <> + class GlossaryTermPlanLimitExceededException <> + } + } + } + + package "Api" as WsApi { + class OrganizationCreatedEvent <> { + + organizationId : String + + ownerId : String + + planLimits : String + + occurredAt : Instant + -- + + {static} of(organizationId : String, ownerId : String) : OrganizationCreatedEvent + } + class ProjectCreatedEvent <> { + + projectId : String + + organizationId : String + + createdBy : String + + occurredAt : Instant + -- + + {static} of(projectId : String, organizationId : String, createdBy : String) : ProjectCreatedEvent + } + } + + package "Domain" { + package "Model" { + package "Events" as WsDomainEvents { + class MemberInvitedEvent <> { + + memberId : String + + organizationId : String + + email : String + + role : String + + occurredAt : Instant + -- + + {static} of(memberId : String, organizationId : String, email : String, role : String) : MemberInvitedEvent + } + class PlanLimitsUpdatedEvent <> { + + organizationId : String + + newLimits : String + + occurredAt : Instant + -- + + {static} of(organizationId : String, newLimits : String) : PlanLimitsUpdatedEvent + } + } + } + } +} + +' ── Inheritance ────────────────────────────────────────────────────── +Organization --|> AbstractAggregateRoot : extends +Member --|> AbstractAggregateRoot : extends +Project --|> AbstractAggregateRoot : extends +ProjectRole --|> AbstractAggregateRoot : extends +ProjectMember --|> AbstractAggregateRoot : extends +ProjectDocument --|> AbstractAggregateRoot : extends +Glossary --|> AbstractAggregateRoot : extends + +OrganizationNotFoundException --|> EntityNotFoundException +MemberNotFoundException --|> EntityNotFoundException +ProjectNotFoundException --|> EntityNotFoundException +ProjectRoleNotFoundException --|> EntityNotFoundException +ProjectMemberNotFoundException --|> EntityNotFoundException +ProjectDocumentNotFoundException --|> EntityNotFoundException +GlossaryNotFoundException --|> EntityNotFoundException +GlossaryTermNotFoundException --|> EntityNotFoundException + +OrganizationSlugAlreadyExistsException --|> BusinessRuleViolationException +MemberAlreadyExistsException --|> BusinessRuleViolationException +MemberPlanLimitExceededException --|> BusinessRuleViolationException +InsufficientPermissionsException --|> BusinessRuleViolationException +ProjectNameAlreadyExistsException --|> BusinessRuleViolationException +ProjectPlanLimitExceededException --|> BusinessRuleViolationException +ProjectRoleNameAlreadyExistsException --|> BusinessRuleViolationException +ProjectMemberAlreadyExistsException --|> BusinessRuleViolationException +DocumentPlanLimitExceededException --|> BusinessRuleViolationException +GlossaryTermPlanLimitExceededException --|> BusinessRuleViolationException + +' ── Dependencies ───────────────────────────────────────────────────── +Organization ..> OrgStatus : uses +Organization ..> PlanLimits : uses +Member ..> OrgRole : uses +Member ..> MemberStatus : uses +Project ..> ProjectStatus : uses +Project ..> TechnicalProfile: uses +ProjectRole ..> Permission : uses +ProjectDocument ..> DocumentStatus : uses + +' ── Cross-aggregate ID references ──────────────────────────────────── +Organization "1" o-- "1" UserId : ownerId (reference) +Member "1" o-- "1" OrganizationId : organizationId (reference) +Member "1" o-- "1" UserId : userId (reference) +Project "1" o-- "1" OrganizationId : organizationId (reference) +ProjectRole "1" o-- "1" ProjectId : projectId (reference) +ProjectMember "1" o-- "1" ProjectId : projectId (reference) +ProjectMember "1" o-- "1" MemberId : memberId (reference) +ProjectMember "1" o-- "1" ProjectRoleId : roleId (reference) +ProjectDocument "1" o-- "1" ProjectId : projectId (reference) +Glossary "1" o-- "1" ProjectId : projectId (reference) + +' ── Domain Events published ─────────────────────────────────────────── +Organization ..> OrganizationCreatedEvent : <> +Organization ..> PlanLimitsUpdatedEvent : <> +Member ..> MemberInvitedEvent : <> +Project ..> ProjectCreatedEvent : <> + +@enduml diff --git a/assets/diagrams/workspace/workspace-component.png b/assets/diagrams/workspace/workspace-component.png new file mode 100644 index 0000000..5bc57be Binary files /dev/null and b/assets/diagrams/workspace/workspace-component.png differ diff --git a/assets/diagrams/workspace/workspace-component.puml b/assets/diagrams/workspace/workspace-component.puml new file mode 100644 index 0000000..4982ba8 --- /dev/null +++ b/assets/diagrams/workspace/workspace-component.puml @@ -0,0 +1,83 @@ +@startuml workspace-component +!include ../c4/C4_Container.puml +!include ../c4/C4_Component.puml +!include ../styles/reqsai-c4-component-style.puml + +AddSystemTag("externalStorage", $bgColor="#37474F", $fontColor="#FFFFFF", $borderColor="#1C2B33", $legendText="External File Storage (S3)") + +title Component Diagram -- Workspace Bounded Context (C4 Level 3) + +' ── External actors ────────────────────────────────────────────────── +Person(user, "Usuario", "Gestiona workspaces, proyectos\ny miembros del equipo.") +System_Ext(iamBC, "IAM BC", "Provee identidad del usuario\nautenticado vía Module API.", $tags="externalBC") +System_Ext(bilBC, "Billing BC", "Verifica limites del plan activo\nvía Module API.", $tags="externalBC") +System_Ext(storage,"File Storage", "S3 / cloud storage para\ndocumentos de proyectos.", $tags="externalStorage") +ContainerDb(postgres,"PostgreSQL", "Database", "workspaces · members · projects\nproject_documents · glossary_terms", $tags="database") + +' ── Workspace Module boundary ──────────────────────────────────────── +Container_Boundary(ws, "Workspace [Spring Modulith Module]") { + + Component(wsCtrl, "WorkspaceController", "REST Controller", "POST /workspaces\nGET /workspaces/{id}\nPATCH /workspaces/{id}\nDELETE /workspaces/{id}", $tags="interface") + Component(projCtrl, "ProjectController", "REST Controller", "POST /workspaces/{id}/projects\nGET /workspaces/{id}/projects\nPATCH /projects/{id}\nDELETE /projects/{id}", $tags="interface") + Component(membCtrl, "MemberController", "REST Controller", "POST /workspaces/{id}/members\nGET /workspaces/{id}/members\nPATCH /members/{id}/role\nDELETE /members/{id}", $tags="interface") + Component(docCtrl, "DocumentController", "REST Controller", "POST /projects/{id}/documents\nGET /projects/{id}/documents\nDELETE /documents/{id}", $tags="interface") + Component(glossCtrl,"GlossaryController", "REST Controller", "POST /workspaces/{id}/glossary\nGET /workspaces/{id}/glossary\nPATCH /glossary/{id}\nDELETE /glossary/{id}", $tags="interface") + + Component(wsCmdHandlers, "Workspace Command Handlers", "CreateWorkspaceCommandHandler\nArchiveWorkspaceCommandHandler\nTransferOwnershipCommandHandler\nUpdateWorkspaceCommandHandler", "Gestiona ciclo de vida del workspace.", $tags="application") + Component(projCmdHandlers,"Project Command Handlers", "CreateProjectCommandHandler\nArchiveProjectCommandHandler\nUpdateProjectCommandHandler", "Gestiona proyectos del workspace.", $tags="application") + Component(membCmdHandlers,"Member Command Handlers", "InviteMemberCommandHandler\nAcceptInvitationCommandHandler\nRemoveMemberCommandHandler\nChangeMemberRoleCommandHandler", "Gestiona invitaciones y roles.", $tags="application") + Component(docCmdHandlers, "Document Command Handlers", "UploadDocumentCommandHandler\nRemoveDocumentCommandHandler", "Gestiona documentos de proyecto.", $tags="application") + Component(glossCmdHandlers,"Glossary Command Handlers", "AddGlossaryTermCommandHandler\nUpdateGlossaryTermCommandHandler\nRemoveGlossaryTermCommandHandler", "Gestiona terminos del glosario.", $tags="application") + Component(queryHandlers, "Query Handlers", "GetWorkspaceQueryHandler\nListWorkspacesQueryHandler\nListProjectsQueryHandler\nListMembersQueryHandler\nGetGlossaryQueryHandler\nListDocumentsQueryHandler", "Consultas de lectura.\n@Transactional(readOnly = true)", $tags="application") + Component(eventListener, "SubscriptionActivatedEventListener", "@ApplicationModuleListener", "Activa funcionalidades del workspace\nal confirmar suscripcion activa.", $tags="application") + + Component(domain, "Domain Model", "Workspace · Member · Project\nProjectDocument · GlossaryTerm\nRole · WorkspaceStatus · MemberStatus\nProjectStatus · DocumentType · Permission", "Logica de negocio pura: invariantes\ny reglas del dominio.", $tags="domain") + + Component(repos, "JPA Repositories", "WorkspaceRepository · MemberRepository\nProjectRepository · ProjectDocumentRepository\nGlossaryTermRepository", "Implementan los repository ports.", $tags="infrastructure") + Component(adapters, "Storage Adapter", "S3FileStorageAdapter", "Implementa el file storage port.\nSube y elimina documentos en S3.", $tags="infrastructure") + Component(moduleApi,"WorkspaceModuleApiImpl", "Module API Facade", "Expone getWorkspaceById()\ngetMemberRole() · isUserMemberOf()\na otros BCs.", $tags="moduleApi") +} + +' ── External relations ─────────────────────────────────────────────── +Rel(user, wsCtrl, "HTTP REST", "JSON / HTTPS") +Rel(user, projCtrl, "HTTP REST", "JSON / HTTPS") +Rel(user, membCtrl, "HTTP REST", "JSON / HTTPS") +Rel(user, docCtrl, "HTTP REST", "JSON / HTTPS") +Rel(user, glossCtrl,"HTTP REST", "JSON / HTTPS") +Rel(iamBC, moduleApi,"In-process","Spring Bean call") +Rel(bilBC, moduleApi,"In-process","Spring Bean call") +Rel(repos, postgres, "Reads / Writes", "JDBC / Hibernate ORM") +Rel(adapters,storage, "Stores files","HTTPS / SDK") + +' ── Internal relations ─────────────────────────────────────────────── +Rel(wsCtrl, wsCmdHandlers, "Delegates to") +Rel(wsCtrl, queryHandlers, "Delegates to") +Rel(projCtrl, projCmdHandlers, "Delegates to") +Rel(projCtrl, queryHandlers, "Delegates to") +Rel(membCtrl, membCmdHandlers, "Delegates to") +Rel(membCtrl, queryHandlers, "Delegates to") +Rel(docCtrl, docCmdHandlers, "Delegates to") +Rel(docCtrl, queryHandlers, "Delegates to") +Rel(glossCtrl,glossCmdHandlers,"Delegates to") +Rel(glossCtrl,queryHandlers, "Delegates to") + +Rel(wsCmdHandlers, domain, "Creates / mutates") +Rel(projCmdHandlers, domain, "Creates / mutates") +Rel(membCmdHandlers, domain, "Creates / mutates") +Rel(docCmdHandlers, domain, "Creates / mutates") +Rel(glossCmdHandlers,domain, "Creates / mutates") +Rel(queryHandlers, domain, "Reads") + +Rel(wsCmdHandlers, repos, "Persists via port") +Rel(projCmdHandlers, repos, "Persists via port") +Rel(membCmdHandlers, repos, "Persists via port") +Rel(docCmdHandlers, repos, "Persists via port") +Rel(docCmdHandlers, adapters, "Stores file via port") +Rel(glossCmdHandlers,repos, "Persists via port") +Rel(queryHandlers, repos, "Reads via port") +Rel(moduleApi, repos, "Reads via port") +Rel(bilBC, eventListener, "Publishes event", "Spring ApplicationEvent") +Rel(eventListener, domain, "Reads / activates") + +SHOW_LEGEND() +@enduml diff --git a/assets/diagrams/workspace/workspace-database.png b/assets/diagrams/workspace/workspace-database.png new file mode 100644 index 0000000..4c1e0d9 Binary files /dev/null and b/assets/diagrams/workspace/workspace-database.png differ diff --git a/assets/diagrams/workspace/workspace-database.puml b/assets/diagrams/workspace/workspace-database.puml new file mode 100644 index 0000000..7a739b6 --- /dev/null +++ b/assets/diagrams/workspace/workspace-database.puml @@ -0,0 +1,212 @@ +@startuml workspace-database +!include ../styles/reqsai-database-style.puml + +title Workspace BC -- Database Design Diagram + +legend top left + |= Clave |= Significado | + |<$REQSAI_BLUE_800> T | Tabla core (entidad) | + |<>| Primary Key | + |<>| Foreign Key | + |<>| NOT NULL | + |<>| UNIQUE | + |<>| INDEX | + |1:1| Relacion 1 a 1 | + |1:N| Relacion 1 a N (0..*) | +endlegend + +note as auditNote + Columnas de auditoria (todas las tablas) + - created_at TIMESTAMP <> + - updated_at TIMESTAMP <> + - created_by VARCHAR(36) + - updated_by VARCHAR(36) +end note + +' ── Table: organizations ───────────────────────────────────────────── +CORE(organizations) { + id : VARCHAR(36) <> + -- + name : VARCHAR(150) <> + slug : VARCHAR(100) <> + owner_id : VARCHAR(36) <> + status : VARCHAR(20) <> + -- + max_members : INT <> + max_projects : INT <> + max_documents_per_project : INT <> + max_tokens_per_month : BIGINT <> + max_glossary_terms_per_project : INT <> + -- +} + +note right of organizations + owner_id + - Ref logica a IAM BC (users) + + Columnas max_* + - Embedding del VO PlanLimits + - Actualizado via SubscriptionAssignedEvent + / SubscriptionUpgradedEvent de Billing BC + + status: ACTIVE | INACTIVE | DELETED +end note + +' ── Table: members ─────────────────────────────────────────────────── +CORE(members) { + id : VARCHAR(36) <> + -- + organization_id : VARCHAR(36) <> + user_id : VARCHAR(36) <> + role : VARCHAR(20) <> + status : VARCHAR(20) <> + invited_by : VARCHAR(36) + invited_at : TIMESTAMP + -- +} + +note right of members + invited_by / invited_at + - NULL para miembros fundadores + - Distintos de los campos de auditoria + + role: OWNER | ADMIN | MEMBER + status: ACTIVE | PENDING | INACTIVE +end note + +' ── Table: projects ────────────────────────────────────────────────── +CORE(projects) { + id : VARCHAR(36) <> + -- + organization_id : VARCHAR(36) <> + name : VARCHAR(150) <> + description : TEXT + status : VARCHAR(20) <> + -- + programming_languages : TEXT + frameworks : TEXT + architecture : VARCHAR(200) + domain : VARCHAR(200) + description_embedding : TEXT + -- +} + +note right of projects + Columnas programming_languages / + frameworks / architecture / domain + - Embedding del VO TechnicalProfile + - JSON arrays serializados como TEXT + + description_embedding + - Vector embedding para busqueda semantica + - JSON array de floats + + status: ACTIVE | ARCHIVED +end note + +' ── Table: project_constraints ─────────────────────────────────────── +CORE(project_constraints) { + id : VARCHAR(36) <> + -- + project_id : VARCHAR(36) <> + description : TEXT <> + embedding : TEXT + -- +} + +' ── Table: project_roles ───────────────────────────────────────────── +CORE(project_roles) { + id : VARCHAR(36) <> + -- + project_id : VARCHAR(36) <> + name : VARCHAR(100) <> + permissions : TEXT <> + -- +} + +note right of project_roles + permissions + - JSON array de valores Permission + - READ_PROJECT | WRITE_PROJECT | etc. +end note + +' ── Table: project_members ─────────────────────────────────────────── +CORE(project_members) { + id : VARCHAR(36) <> + -- + project_id : VARCHAR(36) <> + member_id : VARCHAR(36) <> + role_id : VARCHAR(36) <> + assigned_by : VARCHAR(36) <> + assigned_at : TIMESTAMP <> + -- +} + +' ── Table: project_documents ───────────────────────────────────────── +CORE(project_documents) { + id : VARCHAR(36) <> + -- + project_id : VARCHAR(36) <> + name : VARCHAR(255) <> + url : VARCHAR(500) <> + mime_type : VARCHAR(100) <> + status : VARCHAR(20) <> + -- +} + +' ── Table: glossaries ──────────────────────────────────────────────── +CORE(glossaries) { + id : VARCHAR(36) <> + -- + project_id : VARCHAR(36) <> + -- +} + +note right of glossaries + Creado automaticamente al crear + el proyecto (relacion 1:1). +end note + +' ── Table: glossary_terms ──────────────────────────────────────────── +CORE(glossary_terms) { + id : VARCHAR(36) <> + -- + glossary_id : VARCHAR(36) <> + term : VARCHAR(200) <> + definition : TEXT <> + synonyms : TEXT + added_by : VARCHAR(36) <> + added_at : TIMESTAMP <> + embedding : TEXT + -- +} + +note right of glossary_terms + synonyms / embedding + - JSON arrays serializados como TEXT + - embedding para busqueda semantica +end note + +' ── Relationships ──────────────────────────────────────────────────── +organizations ||--o{ members : "1 org -> 0..* miembros" +organizations ||--o{ projects : "1 org -> 0..* proyectos" +projects ||--o{ project_constraints : "1 proyecto -> 0..* restricciones" +projects ||--o{ project_roles : "1 proyecto -> 0..* roles" +projects ||--o{ project_members : "1 proyecto -> 0..* asignaciones" +projects ||--o{ project_documents : "1 proyecto -> 0..* documentos" +projects ||--|| glossaries : "1 proyecto -> 1 glosario" +glossaries ||--o{ glossary_terms : "1 glosario -> 0..* terminos" +project_roles ||--o{ project_members : "1 rol -> 0..* asignaciones" +members ||--o{ project_members : "1 miembro -> 0..* asignaciones" + +auditNote .. organizations +auditNote .. members +auditNote .. projects +auditNote .. project_roles +auditNote .. project_members +auditNote .. project_documents +auditNote .. glossaries +auditNote .. glossary_terms +auditNote .. project_constraints + +@enduml diff --git a/assets/event-storming/aggregates.jpg b/assets/event-storming/aggregates.jpg index a50efe1..bef1ab1 100644 Binary files a/assets/event-storming/aggregates.jpg and b/assets/event-storming/aggregates.jpg differ diff --git a/assets/ui/landing/mockups/benefits-section-mockup.png b/assets/ui/landing/mockups/benefits-section-mockup.png new file mode 100644 index 0000000..031647d Binary files /dev/null and b/assets/ui/landing/mockups/benefits-section-mockup.png differ diff --git a/assets/ui/landing/mockups/contact-section-mockup.png b/assets/ui/landing/mockups/contact-section-mockup.png new file mode 100644 index 0000000..749b091 Binary files /dev/null and b/assets/ui/landing/mockups/contact-section-mockup.png differ diff --git a/assets/ui/landing/mockups/features-section-mockup.png b/assets/ui/landing/mockups/features-section-mockup.png new file mode 100644 index 0000000..ef3c27d Binary files /dev/null and b/assets/ui/landing/mockups/features-section-mockup.png differ diff --git a/assets/ui/landing/mockups/final-cta-section-mockup.png b/assets/ui/landing/mockups/final-cta-section-mockup.png new file mode 100644 index 0000000..98fefe2 Binary files /dev/null and b/assets/ui/landing/mockups/final-cta-section-mockup.png differ diff --git a/assets/ui/landing/mockups/footer-section-mockup.png b/assets/ui/landing/mockups/footer-section-mockup.png new file mode 100644 index 0000000..f4ba735 Binary files /dev/null and b/assets/ui/landing/mockups/footer-section-mockup.png differ diff --git a/assets/ui/landing/mockups/hero-section-mockup.png b/assets/ui/landing/mockups/hero-section-mockup.png new file mode 100644 index 0000000..7451939 Binary files /dev/null and b/assets/ui/landing/mockups/hero-section-mockup.png differ diff --git a/assets/ui/landing/mockups/pricing-section-mockup.png b/assets/ui/landing/mockups/pricing-section-mockup.png new file mode 100644 index 0000000..8ff6ef4 Binary files /dev/null and b/assets/ui/landing/mockups/pricing-section-mockup.png differ diff --git a/assets/ui/landing/mockups/target-segments-section-mockup.png b/assets/ui/landing/mockups/target-segments-section-mockup.png new file mode 100644 index 0000000..efd1116 Binary files /dev/null and b/assets/ui/landing/mockups/target-segments-section-mockup.png differ diff --git a/assets/ui/landing/mockups/testimonials-section-mockup.png b/assets/ui/landing/mockups/testimonials-section-mockup.png new file mode 100644 index 0000000..494f786 Binary files /dev/null and b/assets/ui/landing/mockups/testimonials-section-mockup.png differ diff --git a/assets/ui/landing/wireframes/benefits-section-wireframe.png b/assets/ui/landing/wireframes/benefits-section-wireframe.png new file mode 100644 index 0000000..fa2f0ac Binary files /dev/null and b/assets/ui/landing/wireframes/benefits-section-wireframe.png differ diff --git a/assets/ui/landing/wireframes/contact-section-wireframe.png b/assets/ui/landing/wireframes/contact-section-wireframe.png new file mode 100644 index 0000000..7b9deb6 Binary files /dev/null and b/assets/ui/landing/wireframes/contact-section-wireframe.png differ diff --git a/assets/ui/landing/wireframes/features-section-wireframe.png b/assets/ui/landing/wireframes/features-section-wireframe.png new file mode 100644 index 0000000..ae682cc Binary files /dev/null and b/assets/ui/landing/wireframes/features-section-wireframe.png differ diff --git a/assets/ui/landing/wireframes/final-cta-section-wireframe.png b/assets/ui/landing/wireframes/final-cta-section-wireframe.png new file mode 100644 index 0000000..9ded19d Binary files /dev/null and b/assets/ui/landing/wireframes/final-cta-section-wireframe.png differ diff --git a/assets/ui/landing/wireframes/footer-section-wireframe.png b/assets/ui/landing/wireframes/footer-section-wireframe.png new file mode 100644 index 0000000..8ad185f Binary files /dev/null and b/assets/ui/landing/wireframes/footer-section-wireframe.png differ diff --git a/assets/ui/landing/wireframes/hero-section-wireframe.png b/assets/ui/landing/wireframes/hero-section-wireframe.png new file mode 100644 index 0000000..a8a877a Binary files /dev/null and b/assets/ui/landing/wireframes/hero-section-wireframe.png differ diff --git a/assets/ui/landing/wireframes/pricing-section-wireframe.png b/assets/ui/landing/wireframes/pricing-section-wireframe.png new file mode 100644 index 0000000..44f59c7 Binary files /dev/null and b/assets/ui/landing/wireframes/pricing-section-wireframe.png differ diff --git a/assets/ui/landing/wireframes/target-segments-section-wireframe.png b/assets/ui/landing/wireframes/target-segments-section-wireframe.png new file mode 100644 index 0000000..5499772 Binary files /dev/null and b/assets/ui/landing/wireframes/target-segments-section-wireframe.png differ diff --git a/assets/ui/landing/wireframes/testimonials-section-wireframe.png b/assets/ui/landing/wireframes/testimonials-section-wireframe.png new file mode 100644 index 0000000..b5fea31 Binary files /dev/null and b/assets/ui/landing/wireframes/testimonials-section-wireframe.png differ diff --git a/assets/ui/mobile/mockups/mobile-billing-subscription-screen.png b/assets/ui/mobile/mockups/mobile-billing-subscription-screen.png new file mode 100644 index 0000000..e15c655 Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-billing-subscription-screen.png differ diff --git a/assets/ui/mobile/mockups/mobile-dashboard-screen.png b/assets/ui/mobile/mockups/mobile-dashboard-screen.png new file mode 100644 index 0000000..ec29ae7 Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-dashboard-screen.png differ diff --git a/assets/ui/mobile/mockups/mobile-integrations-directory-screen.png b/assets/ui/mobile/mockups/mobile-integrations-directory-screen.png new file mode 100644 index 0000000..6992e04 Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-integrations-directory-screen.png differ diff --git a/assets/ui/mobile/mockups/mobile-live-assistant-screen.png b/assets/ui/mobile/mockups/mobile-live-assistant-screen.png new file mode 100644 index 0000000..21d5455 Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-live-assistant-screen.png differ diff --git a/assets/ui/mobile/mockups/mobile-login-screen.png b/assets/ui/mobile/mockups/mobile-login-screen.png new file mode 100644 index 0000000..bdd4aed Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-login-screen.png differ diff --git a/assets/ui/mobile/mockups/mobile-project-settings-screen.png b/assets/ui/mobile/mockups/mobile-project-settings-screen.png new file mode 100644 index 0000000..6468eb7 Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-project-settings-screen.png differ diff --git a/assets/ui/mobile/mockups/mobile-project-user-stories-screen.png b/assets/ui/mobile/mockups/mobile-project-user-stories-screen.png new file mode 100644 index 0000000..a405495 Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-project-user-stories-screen.png differ diff --git a/assets/ui/mobile/mockups/mobile-projects-archive-screen.png b/assets/ui/mobile/mockups/mobile-projects-archive-screen.png new file mode 100644 index 0000000..b1dbb78 Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-projects-archive-screen.png differ diff --git a/assets/ui/mobile/mockups/mobile-review-export-screen.png b/assets/ui/mobile/mockups/mobile-review-export-screen.png new file mode 100644 index 0000000..c518322 Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-review-export-screen.png differ diff --git a/assets/ui/mobile/mockups/mobile-session-history-screen.png b/assets/ui/mobile/mockups/mobile-session-history-screen.png new file mode 100644 index 0000000..2f9cc8a Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-session-history-screen.png differ diff --git a/assets/ui/mobile/mockups/mobile-settings-profile-screen.png b/assets/ui/mobile/mockups/mobile-settings-profile-screen.png new file mode 100644 index 0000000..06c151e Binary files /dev/null and b/assets/ui/mobile/mockups/mobile-settings-profile-screen.png differ diff --git a/assets/ui/mobile/prototype/mobile-application-prototype.png b/assets/ui/mobile/prototype/mobile-application-prototype.png new file mode 100644 index 0000000..650fa8f Binary files /dev/null and b/assets/ui/mobile/prototype/mobile-application-prototype.png differ diff --git a/assets/ui/mobile/userflows/mobile-authentication-profile-user-flow.png b/assets/ui/mobile/userflows/mobile-authentication-profile-user-flow.png new file mode 100644 index 0000000..5c43b14 Binary files /dev/null and b/assets/ui/mobile/userflows/mobile-authentication-profile-user-flow.png differ diff --git a/assets/ui/mobile/userflows/mobile-live-assistant-user-flow.png b/assets/ui/mobile/userflows/mobile-live-assistant-user-flow.png new file mode 100644 index 0000000..7778987 Binary files /dev/null and b/assets/ui/mobile/userflows/mobile-live-assistant-user-flow.png differ diff --git a/assets/ui/mobile/userflows/mobile-project-management-user-flow.png b/assets/ui/mobile/userflows/mobile-project-management-user-flow.png new file mode 100644 index 0000000..e938795 Binary files /dev/null and b/assets/ui/mobile/userflows/mobile-project-management-user-flow.png differ diff --git a/assets/ui/mobile/userflows/mobile-review-export-user-flow.png b/assets/ui/mobile/userflows/mobile-review-export-user-flow.png new file mode 100644 index 0000000..0f91155 Binary files /dev/null and b/assets/ui/mobile/userflows/mobile-review-export-user-flow.png differ diff --git a/assets/ui/mobile/userflows/mobile-user-stories-to-history-integrations-billing-user-flow.png b/assets/ui/mobile/userflows/mobile-user-stories-to-history-integrations-billing-user-flow.png new file mode 100644 index 0000000..86f6908 Binary files /dev/null and b/assets/ui/mobile/userflows/mobile-user-stories-to-history-integrations-billing-user-flow.png differ diff --git a/assets/ui/mobile/wireflows/mobile-dashboard-to-live-assistant.png b/assets/ui/mobile/wireflows/mobile-dashboard-to-live-assistant.png new file mode 100644 index 0000000..4437f18 Binary files /dev/null and b/assets/ui/mobile/wireflows/mobile-dashboard-to-live-assistant.png differ diff --git a/assets/ui/mobile/wireflows/mobile-dashboard-to-review-export.png b/assets/ui/mobile/wireflows/mobile-dashboard-to-review-export.png new file mode 100644 index 0000000..62462d3 Binary files /dev/null and b/assets/ui/mobile/wireflows/mobile-dashboard-to-review-export.png differ diff --git a/assets/ui/mobile/wireflows/mobile-login-to-profile-management.png b/assets/ui/mobile/wireflows/mobile-login-to-profile-management.png new file mode 100644 index 0000000..0770636 Binary files /dev/null and b/assets/ui/mobile/wireflows/mobile-login-to-profile-management.png differ diff --git a/assets/ui/mobile/wireflows/mobile-project-management-flow.png b/assets/ui/mobile/wireflows/mobile-project-management-flow.png new file mode 100644 index 0000000..2b4c10a Binary files /dev/null and b/assets/ui/mobile/wireflows/mobile-project-management-flow.png differ diff --git a/assets/ui/mobile/wireflows/mobile-user-stories-to-history-integrations-billing.png b/assets/ui/mobile/wireflows/mobile-user-stories-to-history-integrations-billing.png new file mode 100644 index 0000000..9f10540 Binary files /dev/null and b/assets/ui/mobile/wireflows/mobile-user-stories-to-history-integrations-billing.png differ diff --git a/assets/ui/mobile/wireframes/mobile-billing-subscription-screen.png b/assets/ui/mobile/wireframes/mobile-billing-subscription-screen.png new file mode 100644 index 0000000..6224140 Binary files /dev/null and b/assets/ui/mobile/wireframes/mobile-billing-subscription-screen.png differ diff --git a/assets/ui/mobile/wireframes/mobile-dashboard-screen.png b/assets/ui/mobile/wireframes/mobile-dashboard-screen.png new file mode 100644 index 0000000..a47bcb7 Binary files /dev/null and b/assets/ui/mobile/wireframes/mobile-dashboard-screen.png differ diff --git a/assets/ui/mobile/wireframes/mobile-integrations-directory-screen.png b/assets/ui/mobile/wireframes/mobile-integrations-directory-screen.png new file mode 100644 index 0000000..3466cb1 Binary files /dev/null and b/assets/ui/mobile/wireframes/mobile-integrations-directory-screen.png differ diff --git a/assets/ui/mobile/wireframes/mobile-live-assistant-screen.png b/assets/ui/mobile/wireframes/mobile-live-assistant-screen.png new file mode 100644 index 0000000..fe82848 Binary files /dev/null and b/assets/ui/mobile/wireframes/mobile-live-assistant-screen.png differ diff --git a/assets/ui/mobile/wireframes/mobile-login-screen.png b/assets/ui/mobile/wireframes/mobile-login-screen.png new file mode 100644 index 0000000..14552fb Binary files /dev/null and b/assets/ui/mobile/wireframes/mobile-login-screen.png differ diff --git a/assets/ui/mobile/wireframes/mobile-project-settings-screen.png b/assets/ui/mobile/wireframes/mobile-project-settings-screen.png new file mode 100644 index 0000000..2a56200 Binary files /dev/null and b/assets/ui/mobile/wireframes/mobile-project-settings-screen.png differ diff --git a/assets/ui/mobile/wireframes/mobile-projects-archive-screen.png b/assets/ui/mobile/wireframes/mobile-projects-archive-screen.png new file mode 100644 index 0000000..88c1252 Binary files /dev/null and b/assets/ui/mobile/wireframes/mobile-projects-archive-screen.png differ diff --git a/assets/ui/mobile/wireframes/mobile-review-export-screen.png b/assets/ui/mobile/wireframes/mobile-review-export-screen.png new file mode 100644 index 0000000..b1c4339 Binary files /dev/null and b/assets/ui/mobile/wireframes/mobile-review-export-screen.png differ diff --git a/assets/ui/mobile/wireframes/mobile-session-history-screen.png b/assets/ui/mobile/wireframes/mobile-session-history-screen.png new file mode 100644 index 0000000..a99a3e3 Binary files /dev/null and b/assets/ui/mobile/wireframes/mobile-session-history-screen.png differ diff --git a/assets/ui/mobile/wireframes/mobile-settings-profile-screen.png b/assets/ui/mobile/wireframes/mobile-settings-profile-screen.png new file mode 100644 index 0000000..c013a98 Binary files /dev/null and b/assets/ui/mobile/wireframes/mobile-settings-profile-screen.png differ diff --git a/assets/ui/mockups/billing-subscription-page.png b/assets/ui/mockups/billing-subscription-page.png new file mode 100644 index 0000000..5f18d86 Binary files /dev/null and b/assets/ui/mockups/billing-subscription-page.png differ diff --git a/assets/ui/mockups/create-new-project-modal-template-selection.png b/assets/ui/mockups/create-new-project-modal-template-selection.png new file mode 100644 index 0000000..d0f850c Binary files /dev/null and b/assets/ui/mockups/create-new-project-modal-template-selection.png differ diff --git a/assets/ui/mockups/dashboard-empty-state-before-workspace.png b/assets/ui/mockups/dashboard-empty-state-before-workspace.png new file mode 100644 index 0000000..65ade44 Binary files /dev/null and b/assets/ui/mockups/dashboard-empty-state-before-workspace.png differ diff --git a/assets/ui/mockups/discovery-sessions-page-metrics-and-export.png b/assets/ui/mockups/discovery-sessions-page-metrics-and-export.png new file mode 100644 index 0000000..195a5f6 Binary files /dev/null and b/assets/ui/mockups/discovery-sessions-page-metrics-and-export.png differ diff --git a/assets/ui/mockups/discovery-sessions-page-simple-overview.png b/assets/ui/mockups/discovery-sessions-page-simple-overview.png new file mode 100644 index 0000000..e0869a0 Binary files /dev/null and b/assets/ui/mockups/discovery-sessions-page-simple-overview.png differ diff --git a/assets/ui/mockups/google-auth-external-authorization.png b/assets/ui/mockups/google-auth-external-authorization.png new file mode 100644 index 0000000..c6cad8c Binary files /dev/null and b/assets/ui/mockups/google-auth-external-authorization.png differ diff --git a/assets/ui/mockups/integrations-page-jira-connection.png b/assets/ui/mockups/integrations-page-jira-connection.png new file mode 100644 index 0000000..006c2d9 Binary files /dev/null and b/assets/ui/mockups/integrations-page-jira-connection.png differ diff --git a/assets/ui/mockups/jira-connection-modal-oauth-flow.png b/assets/ui/mockups/jira-connection-modal-oauth-flow.png new file mode 100644 index 0000000..46d5afc Binary files /dev/null and b/assets/ui/mockups/jira-connection-modal-oauth-flow.png differ diff --git a/assets/ui/mockups/live-discovery-session-modal-configuration.png b/assets/ui/mockups/live-discovery-session-modal-configuration.png new file mode 100644 index 0000000..4d36572 Binary files /dev/null and b/assets/ui/mockups/live-discovery-session-modal-configuration.png differ diff --git a/assets/ui/mockups/login-screen.png b/assets/ui/mockups/login-screen.png new file mode 100644 index 0000000..d43a9fe Binary files /dev/null and b/assets/ui/mockups/login-screen.png differ diff --git a/assets/ui/mockups/navigation-consistency-check-frame.png b/assets/ui/mockups/navigation-consistency-check-frame.png new file mode 100644 index 0000000..8e8dd9e Binary files /dev/null and b/assets/ui/mockups/navigation-consistency-check-frame.png differ diff --git a/assets/ui/mockups/projects-board-overview.png b/assets/ui/mockups/projects-board-overview.png new file mode 100644 index 0000000..279df62 Binary files /dev/null and b/assets/ui/mockups/projects-board-overview.png differ diff --git a/assets/ui/mockups/projects-page-overview.png b/assets/ui/mockups/projects-page-overview.png new file mode 100644 index 0000000..94cd78a Binary files /dev/null and b/assets/ui/mockups/projects-page-overview.png differ diff --git a/assets/ui/mockups/reqs-ai-logo-icon.png b/assets/ui/mockups/reqs-ai-logo-icon.png new file mode 100644 index 0000000..039be83 Binary files /dev/null and b/assets/ui/mockups/reqs-ai-logo-icon.png differ diff --git a/assets/ui/mockups/settings-team-members-management-page.png b/assets/ui/mockups/settings-team-members-management-page.png new file mode 100644 index 0000000..2a25226 Binary files /dev/null and b/assets/ui/mockups/settings-team-members-management-page.png differ diff --git a/assets/ui/mockups/settings-workspace-configuration-page.png b/assets/ui/mockups/settings-workspace-configuration-page.png new file mode 100644 index 0000000..5b04c3d Binary files /dev/null and b/assets/ui/mockups/settings-workspace-configuration-page.png differ diff --git a/assets/ui/mockups/signup-screen.png b/assets/ui/mockups/signup-screen.png new file mode 100644 index 0000000..eabd7ad Binary files /dev/null and b/assets/ui/mockups/signup-screen.png differ diff --git a/assets/ui/mockups/user-profile-menu-open.png b/assets/ui/mockups/user-profile-menu-open.png new file mode 100644 index 0000000..00e4988 Binary files /dev/null and b/assets/ui/mockups/user-profile-menu-open.png differ diff --git a/assets/ui/mockups/user-stories-page-review-board.png b/assets/ui/mockups/user-stories-page-review-board.png new file mode 100644 index 0000000..7d99e76 Binary files /dev/null and b/assets/ui/mockups/user-stories-page-review-board.png differ diff --git a/assets/ui/mockups/user-story-review-drawer-with-gherkin.png b/assets/ui/mockups/user-story-review-drawer-with-gherkin.png new file mode 100644 index 0000000..4749c3d Binary files /dev/null and b/assets/ui/mockups/user-story-review-drawer-with-gherkin.png differ diff --git a/assets/ui/mockups/workspace-created-success-details-modal.png b/assets/ui/mockups/workspace-created-success-details-modal.png new file mode 100644 index 0000000..f895e59 Binary files /dev/null and b/assets/ui/mockups/workspace-created-success-details-modal.png differ diff --git a/assets/ui/mockups/workspace-created-success-modal.png b/assets/ui/mockups/workspace-created-success-modal.png new file mode 100644 index 0000000..dde77d9 Binary files /dev/null and b/assets/ui/mockups/workspace-created-success-modal.png differ diff --git a/assets/ui/mockups/workspace-creation-company-type-dropdown-open.png b/assets/ui/mockups/workspace-creation-company-type-dropdown-open.png new file mode 100644 index 0000000..5e4ae1b Binary files /dev/null and b/assets/ui/mockups/workspace-creation-company-type-dropdown-open.png differ diff --git a/assets/ui/mockups/workspace-creation-form-filled-private-visibility.png b/assets/ui/mockups/workspace-creation-form-filled-private-visibility.png new file mode 100644 index 0000000..17816e2 Binary files /dev/null and b/assets/ui/mockups/workspace-creation-form-filled-private-visibility.png differ diff --git a/assets/ui/mockups/workspace-creation-loading-state.png b/assets/ui/mockups/workspace-creation-loading-state.png new file mode 100644 index 0000000..441d309 Binary files /dev/null and b/assets/ui/mockups/workspace-creation-loading-state.png differ diff --git a/assets/ui/mockups/workspace-creation-modal-empty-fields.png b/assets/ui/mockups/workspace-creation-modal-empty-fields.png new file mode 100644 index 0000000..d6c6ed6 Binary files /dev/null and b/assets/ui/mockups/workspace-creation-modal-empty-fields.png differ diff --git a/assets/ui/mockups/workspace-creation-progress-loading-state.png b/assets/ui/mockups/workspace-creation-progress-loading-state.png new file mode 100644 index 0000000..230b6eb Binary files /dev/null and b/assets/ui/mockups/workspace-creation-progress-loading-state.png differ diff --git a/assets/ui/mockups/workspace-creation-team-size-dropdown-open.png b/assets/ui/mockups/workspace-creation-team-size-dropdown-open.png new file mode 100644 index 0000000..e5918df Binary files /dev/null and b/assets/ui/mockups/workspace-creation-team-size-dropdown-open.png differ diff --git a/assets/ui/mockups/workspace-creation-validation-errors.png b/assets/ui/mockups/workspace-creation-validation-errors.png new file mode 100644 index 0000000..ac941fa Binary files /dev/null and b/assets/ui/mockups/workspace-creation-validation-errors.png differ diff --git a/assets/ui/mockups/workspace-dashboard-home.png b/assets/ui/mockups/workspace-dashboard-home.png new file mode 100644 index 0000000..d477b27 Binary files /dev/null and b/assets/ui/mockups/workspace-dashboard-home.png differ diff --git a/assets/ui/mockups/workspace-onboarding-use-case-selection-state.png b/assets/ui/mockups/workspace-onboarding-use-case-selection-state.png new file mode 100644 index 0000000..d095250 Binary files /dev/null and b/assets/ui/mockups/workspace-onboarding-use-case-selection-state.png differ diff --git a/assets/ui/mockups/workspace-settings-company-type-dropdown-open.png b/assets/ui/mockups/workspace-settings-company-type-dropdown-open.png new file mode 100644 index 0000000..722fd98 Binary files /dev/null and b/assets/ui/mockups/workspace-settings-company-type-dropdown-open.png differ diff --git a/assets/ui/mockups/workspace-setup-summary-form-filled.png b/assets/ui/mockups/workspace-setup-summary-form-filled.png new file mode 100644 index 0000000..87257f8 Binary files /dev/null and b/assets/ui/mockups/workspace-setup-summary-form-filled.png differ diff --git a/assets/ui/mockups/workspace-switcher-menu-open.png b/assets/ui/mockups/workspace-switcher-menu-open.png new file mode 100644 index 0000000..bd5747b Binary files /dev/null and b/assets/ui/mockups/workspace-switcher-menu-open.png differ diff --git a/assets/ui/mockups/workspace-use-case-selection-modal.png b/assets/ui/mockups/workspace-use-case-selection-modal.png new file mode 100644 index 0000000..1d4ef4a Binary files /dev/null and b/assets/ui/mockups/workspace-use-case-selection-modal.png differ diff --git a/assets/ui/style-guidelines/color-palette.png b/assets/ui/style-guidelines/color-palette.png new file mode 100644 index 0000000..c0236e1 Binary files /dev/null and b/assets/ui/style-guidelines/color-palette.png differ diff --git a/assets/ui/style-guidelines/component-style-examples.png b/assets/ui/style-guidelines/component-style-examples.png new file mode 100644 index 0000000..0980f6a Binary files /dev/null and b/assets/ui/style-guidelines/component-style-examples.png differ diff --git a/assets/ui/style-guidelines/information-architecture-map.png b/assets/ui/style-guidelines/information-architecture-map.png new file mode 100644 index 0000000..040faf5 Binary files /dev/null and b/assets/ui/style-guidelines/information-architecture-map.png differ diff --git a/assets/ui/style-guidelines/labeling-system.png b/assets/ui/style-guidelines/labeling-system.png new file mode 100644 index 0000000..c489a1c Binary files /dev/null and b/assets/ui/style-guidelines/labeling-system.png differ diff --git a/assets/ui/style-guidelines/navigation-system.png b/assets/ui/style-guidelines/navigation-system.png new file mode 100644 index 0000000..dc93f98 Binary files /dev/null and b/assets/ui/style-guidelines/navigation-system.png differ diff --git a/assets/ui/style-guidelines/searching-system.png b/assets/ui/style-guidelines/searching-system.png new file mode 100644 index 0000000..002f15f Binary files /dev/null and b/assets/ui/style-guidelines/searching-system.png differ diff --git a/assets/ui/style-guidelines/seo-meta-tags.png b/assets/ui/style-guidelines/seo-meta-tags.png new file mode 100644 index 0000000..cf5ea2d Binary files /dev/null and b/assets/ui/style-guidelines/seo-meta-tags.png differ diff --git a/assets/ui/style-guidelines/typography-scale.png b/assets/ui/style-guidelines/typography-scale.png new file mode 100644 index 0000000..fb06703 Binary files /dev/null and b/assets/ui/style-guidelines/typography-scale.png differ diff --git a/assets/ui/web/prototype/web-application-prototype.png b/assets/ui/web/prototype/web-application-prototype.png new file mode 100644 index 0000000..e8ee684 Binary files /dev/null and b/assets/ui/web/prototype/web-application-prototype.png differ diff --git a/assets/ui/web/user-flows/01-authentication-signup-to-login-user-flow.png b/assets/ui/web/user-flows/01-authentication-signup-to-login-user-flow.png new file mode 100644 index 0000000..151bdb7 Binary files /dev/null and b/assets/ui/web/user-flows/01-authentication-signup-to-login-user-flow.png differ diff --git a/assets/ui/web/user-flows/02-onboarding-empty-dashboard-to-workspace-setup-user-flow.png b/assets/ui/web/user-flows/02-onboarding-empty-dashboard-to-workspace-setup-user-flow.png new file mode 100644 index 0000000..c4c38a5 Binary files /dev/null and b/assets/ui/web/user-flows/02-onboarding-empty-dashboard-to-workspace-setup-user-flow.png differ diff --git a/assets/ui/web/user-flows/03-workspace-creation-loading-to-success-user-flow.png b/assets/ui/web/user-flows/03-workspace-creation-loading-to-success-user-flow.png new file mode 100644 index 0000000..a7bcfda Binary files /dev/null and b/assets/ui/web/user-flows/03-workspace-creation-loading-to-success-user-flow.png differ diff --git a/assets/ui/web/user-flows/04-workspace-home-to-projects-user-flow.png b/assets/ui/web/user-flows/04-workspace-home-to-projects-user-flow.png new file mode 100644 index 0000000..36f5350 Binary files /dev/null and b/assets/ui/web/user-flows/04-workspace-home-to-projects-user-flow.png differ diff --git a/assets/ui/web/user-flows/05-sessions-to-discovery-sessions-user-flow.png b/assets/ui/web/user-flows/05-sessions-to-discovery-sessions-user-flow.png new file mode 100644 index 0000000..4b6f1a9 Binary files /dev/null and b/assets/ui/web/user-flows/05-sessions-to-discovery-sessions-user-flow.png differ diff --git a/assets/ui/web/user-flows/06-discovery-sessions-to-live-session-modal-user-flow.png b/assets/ui/web/user-flows/06-discovery-sessions-to-live-session-modal-user-flow.png new file mode 100644 index 0000000..b5806bd Binary files /dev/null and b/assets/ui/web/user-flows/06-discovery-sessions-to-live-session-modal-user-flow.png differ diff --git a/assets/ui/web/user-flows/07-user-stories-to-story-review-user-flow.png b/assets/ui/web/user-flows/07-user-stories-to-story-review-user-flow.png new file mode 100644 index 0000000..a573e19 Binary files /dev/null and b/assets/ui/web/user-flows/07-user-stories-to-story-review-user-flow.png differ diff --git a/assets/ui/web/user-flows/08-integrations-to-jira-connection-user-flow.png b/assets/ui/web/user-flows/08-integrations-to-jira-connection-user-flow.png new file mode 100644 index 0000000..7cb4714 Binary files /dev/null and b/assets/ui/web/user-flows/08-integrations-to-jira-connection-user-flow.png differ diff --git a/assets/ui/web/user-flows/09-billing-subscription-management-user-flow.png b/assets/ui/web/user-flows/09-billing-subscription-management-user-flow.png new file mode 100644 index 0000000..47aa786 Binary files /dev/null and b/assets/ui/web/user-flows/09-billing-subscription-management-user-flow.png differ diff --git a/assets/ui/web/user-flows/10-settings-to-team-management-user-flow.png b/assets/ui/web/user-flows/10-settings-to-team-management-user-flow.png new file mode 100644 index 0000000..3f0859b Binary files /dev/null and b/assets/ui/web/user-flows/10-settings-to-team-management-user-flow.png differ diff --git a/assets/ui/web/user-flows/11-complete-web-application-user-flow-overview.png b/assets/ui/web/user-flows/11-complete-web-application-user-flow-overview.png new file mode 100644 index 0000000..f1a6f68 Binary files /dev/null and b/assets/ui/web/user-flows/11-complete-web-application-user-flow-overview.png differ diff --git a/assets/ui/wireflows/billing-to-settings-page.png b/assets/ui/wireflows/billing-to-settings-page.png new file mode 100644 index 0000000..9800f2c Binary files /dev/null and b/assets/ui/wireflows/billing-to-settings-page.png differ diff --git a/assets/ui/wireflows/company-type-dropdown-to-use-case-selection.png b/assets/ui/wireflows/company-type-dropdown-to-use-case-selection.png new file mode 100644 index 0000000..ffc5734 Binary files /dev/null and b/assets/ui/wireflows/company-type-dropdown-to-use-case-selection.png differ diff --git a/assets/ui/wireflows/company-type-selection-to-primary-use-case.png b/assets/ui/wireflows/company-type-selection-to-primary-use-case.png new file mode 100644 index 0000000..58783a6 Binary files /dev/null and b/assets/ui/wireflows/company-type-selection-to-primary-use-case.png differ diff --git a/assets/ui/wireflows/create-workspace-to-company-type-dropdown.png b/assets/ui/wireflows/create-workspace-to-company-type-dropdown.png new file mode 100644 index 0000000..a7ee260 Binary files /dev/null and b/assets/ui/wireflows/create-workspace-to-company-type-dropdown.png differ diff --git a/assets/ui/wireflows/empty-workspace-to-create-workspace-modal.png b/assets/ui/wireflows/empty-workspace-to-create-workspace-modal.png new file mode 100644 index 0000000..8f6a291 Binary files /dev/null and b/assets/ui/wireflows/empty-workspace-to-create-workspace-modal.png differ diff --git a/assets/ui/wireflows/integrations-to-jira-connection-modal.png b/assets/ui/wireflows/integrations-to-jira-connection-modal.png new file mode 100644 index 0000000..89a7f59 Binary files /dev/null and b/assets/ui/wireflows/integrations-to-jira-connection-modal.png differ diff --git a/assets/ui/wireflows/login-to-google-auth.png b/assets/ui/wireflows/login-to-google-auth.png new file mode 100644 index 0000000..c364a3f Binary files /dev/null and b/assets/ui/wireflows/login-to-google-auth.png differ diff --git a/assets/ui/wireflows/login-to-workspace-created-success.png b/assets/ui/wireflows/login-to-workspace-created-success.png new file mode 100644 index 0000000..fcf26b6 Binary files /dev/null and b/assets/ui/wireflows/login-to-workspace-created-success.png differ diff --git a/assets/ui/wireflows/profile-menu-to-sessions-page.png b/assets/ui/wireflows/profile-menu-to-sessions-page.png new file mode 100644 index 0000000..0d7316d Binary files /dev/null and b/assets/ui/wireflows/profile-menu-to-sessions-page.png differ diff --git a/assets/ui/wireflows/projects-page-to-create-project-modal.png b/assets/ui/wireflows/projects-page-to-create-project-modal.png new file mode 100644 index 0000000..e14aca1 Binary files /dev/null and b/assets/ui/wireflows/projects-page-to-create-project-modal.png differ diff --git a/assets/ui/wireflows/sessions-page-to-discovery-sessions-overview.png b/assets/ui/wireflows/sessions-page-to-discovery-sessions-overview.png new file mode 100644 index 0000000..d87869b Binary files /dev/null and b/assets/ui/wireflows/sessions-page-to-discovery-sessions-overview.png differ diff --git a/assets/ui/wireflows/sessions-to-integrations-page.png b/assets/ui/wireflows/sessions-to-integrations-page.png new file mode 100644 index 0000000..75db952 Binary files /dev/null and b/assets/ui/wireflows/sessions-to-integrations-page.png differ diff --git a/assets/ui/wireflows/settings-to-team-management.png b/assets/ui/wireflows/settings-to-team-management.png new file mode 100644 index 0000000..aac47d9 Binary files /dev/null and b/assets/ui/wireflows/settings-to-team-management.png differ diff --git a/assets/ui/wireflows/signup-to-google-auth.png b/assets/ui/wireflows/signup-to-google-auth.png new file mode 100644 index 0000000..624bb83 Binary files /dev/null and b/assets/ui/wireflows/signup-to-google-auth.png differ diff --git a/assets/ui/wireflows/signup-to-login.png b/assets/ui/wireflows/signup-to-login.png new file mode 100644 index 0000000..a4f8ddf Binary files /dev/null and b/assets/ui/wireflows/signup-to-login.png differ diff --git a/assets/ui/wireflows/start-live-session-modal.png b/assets/ui/wireflows/start-live-session-modal.png new file mode 100644 index 0000000..f8b6bf4 Binary files /dev/null and b/assets/ui/wireflows/start-live-session-modal.png differ diff --git a/assets/ui/wireflows/team-management-to-home-notifications.png b/assets/ui/wireflows/team-management-to-home-notifications.png new file mode 100644 index 0000000..7418446 Binary files /dev/null and b/assets/ui/wireflows/team-management-to-home-notifications.png differ diff --git a/assets/ui/wireflows/team-size-dropdown-to-primary-use-cases.png b/assets/ui/wireflows/team-size-dropdown-to-primary-use-cases.png new file mode 100644 index 0000000..1d4e49f Binary files /dev/null and b/assets/ui/wireflows/team-size-dropdown-to-primary-use-cases.png differ diff --git a/assets/ui/wireflows/use-case-selection-to-workspace-creation-form.png b/assets/ui/wireflows/use-case-selection-to-workspace-creation-form.png new file mode 100644 index 0000000..70dc2b5 Binary files /dev/null and b/assets/ui/wireflows/use-case-selection-to-workspace-creation-form.png differ diff --git a/assets/ui/wireflows/user-stories-to-story-review-drawer.png b/assets/ui/wireflows/user-stories-to-story-review-drawer.png new file mode 100644 index 0000000..3b73fb8 Binary files /dev/null and b/assets/ui/wireflows/user-stories-to-story-review-drawer.png differ diff --git a/assets/ui/wireflows/workspace-created-to-company-type-settings.png b/assets/ui/wireflows/workspace-created-to-company-type-settings.png new file mode 100644 index 0000000..bffa007 Binary files /dev/null and b/assets/ui/wireflows/workspace-created-to-company-type-settings.png differ diff --git a/assets/ui/wireflows/workspace-created-to-workspace-building-loading.png b/assets/ui/wireflows/workspace-created-to-workspace-building-loading.png new file mode 100644 index 0000000..319487c Binary files /dev/null and b/assets/ui/wireflows/workspace-created-to-workspace-building-loading.png differ diff --git a/assets/ui/wireflows/workspace-creation-progress-to-success.png b/assets/ui/wireflows/workspace-creation-progress-to-success.png new file mode 100644 index 0000000..80d2994 Binary files /dev/null and b/assets/ui/wireflows/workspace-creation-progress-to-success.png differ diff --git a/assets/ui/wireflows/workspace-form-to-new-workspace-validation.png b/assets/ui/wireflows/workspace-form-to-new-workspace-validation.png new file mode 100644 index 0000000..3818102 Binary files /dev/null and b/assets/ui/wireflows/workspace-form-to-new-workspace-validation.png differ diff --git a/assets/ui/wireflows/workspace-home-to-user-stories.png b/assets/ui/wireflows/workspace-home-to-user-stories.png new file mode 100644 index 0000000..9064f9b Binary files /dev/null and b/assets/ui/wireflows/workspace-home-to-user-stories.png differ diff --git a/assets/ui/wireflows/workspace-success-to-workspace-home.png b/assets/ui/wireflows/workspace-success-to-workspace-home.png new file mode 100644 index 0000000..ffe75ec Binary files /dev/null and b/assets/ui/wireflows/workspace-success-to-workspace-home.png differ diff --git a/assets/ui/wireflows/workspace-switcher-to-projects-page.png b/assets/ui/wireflows/workspace-switcher-to-projects-page.png new file mode 100644 index 0000000..4e091f0 Binary files /dev/null and b/assets/ui/wireflows/workspace-switcher-to-projects-page.png differ diff --git a/assets/ui/wireflows/workspace-switcher-to-user-profile-menu.png b/assets/ui/wireflows/workspace-switcher-to-user-profile-menu.png new file mode 100644 index 0000000..485a210 Binary files /dev/null and b/assets/ui/wireflows/workspace-switcher-to-user-profile-menu.png differ diff --git a/assets/ui/wireframes/billing-subscription-page.png b/assets/ui/wireframes/billing-subscription-page.png new file mode 100644 index 0000000..977efc3 Binary files /dev/null and b/assets/ui/wireframes/billing-subscription-page.png differ diff --git a/assets/ui/wireframes/create-new-project-modal.png b/assets/ui/wireframes/create-new-project-modal.png new file mode 100644 index 0000000..b865a4b Binary files /dev/null and b/assets/ui/wireframes/create-new-project-modal.png differ diff --git a/assets/ui/wireframes/dashboard-empty-state-before-workspace.png b/assets/ui/wireframes/dashboard-empty-state-before-workspace.png new file mode 100644 index 0000000..3f19e72 Binary files /dev/null and b/assets/ui/wireframes/dashboard-empty-state-before-workspace.png differ diff --git a/assets/ui/wireframes/discovery-sessions-page.png b/assets/ui/wireframes/discovery-sessions-page.png new file mode 100644 index 0000000..5a04993 Binary files /dev/null and b/assets/ui/wireframes/discovery-sessions-page.png differ diff --git a/assets/ui/wireframes/google-auth-external-authorization.png b/assets/ui/wireframes/google-auth-external-authorization.png new file mode 100644 index 0000000..f207c92 Binary files /dev/null and b/assets/ui/wireframes/google-auth-external-authorization.png differ diff --git a/assets/ui/wireframes/integrations-page-jira-connection.png b/assets/ui/wireframes/integrations-page-jira-connection.png new file mode 100644 index 0000000..08cb018 Binary files /dev/null and b/assets/ui/wireframes/integrations-page-jira-connection.png differ diff --git a/assets/ui/wireframes/jira-connection-modal.png b/assets/ui/wireframes/jira-connection-modal.png new file mode 100644 index 0000000..665bf34 Binary files /dev/null and b/assets/ui/wireframes/jira-connection-modal.png differ diff --git a/assets/ui/wireframes/live-session-configuration-modal.png b/assets/ui/wireframes/live-session-configuration-modal.png new file mode 100644 index 0000000..d65fbde Binary files /dev/null and b/assets/ui/wireframes/live-session-configuration-modal.png differ diff --git a/assets/ui/wireframes/login-screen.png b/assets/ui/wireframes/login-screen.png new file mode 100644 index 0000000..224c8d1 Binary files /dev/null and b/assets/ui/wireframes/login-screen.png differ diff --git a/assets/ui/wireframes/navigation-consistency-check-frame.png b/assets/ui/wireframes/navigation-consistency-check-frame.png new file mode 100644 index 0000000..99c114e Binary files /dev/null and b/assets/ui/wireframes/navigation-consistency-check-frame.png differ diff --git a/assets/ui/wireframes/projects-page-overview.png b/assets/ui/wireframes/projects-page-overview.png new file mode 100644 index 0000000..1bea3f1 Binary files /dev/null and b/assets/ui/wireframes/projects-page-overview.png differ diff --git a/assets/ui/wireframes/settings-team-members-management.png b/assets/ui/wireframes/settings-team-members-management.png new file mode 100644 index 0000000..9f8e666 Binary files /dev/null and b/assets/ui/wireframes/settings-team-members-management.png differ diff --git a/assets/ui/wireframes/settings-workspace-configuration.png b/assets/ui/wireframes/settings-workspace-configuration.png new file mode 100644 index 0000000..77425c8 Binary files /dev/null and b/assets/ui/wireframes/settings-workspace-configuration.png differ diff --git a/assets/ui/wireframes/signup-screen.png b/assets/ui/wireframes/signup-screen.png new file mode 100644 index 0000000..d411746 Binary files /dev/null and b/assets/ui/wireframes/signup-screen.png differ diff --git a/assets/ui/wireframes/user-profile-menu-open.png b/assets/ui/wireframes/user-profile-menu-open.png new file mode 100644 index 0000000..9189a5c Binary files /dev/null and b/assets/ui/wireframes/user-profile-menu-open.png differ diff --git a/assets/ui/wireframes/user-stories-review-board.png b/assets/ui/wireframes/user-stories-review-board.png new file mode 100644 index 0000000..38e27ac Binary files /dev/null and b/assets/ui/wireframes/user-stories-review-board.png differ diff --git a/assets/ui/wireframes/user-story-detail-review-drawer.png b/assets/ui/wireframes/user-story-detail-review-drawer.png new file mode 100644 index 0000000..e7eceed Binary files /dev/null and b/assets/ui/wireframes/user-story-detail-review-drawer.png differ diff --git a/assets/ui/wireframes/workspace-company-type-dropdown-open.png b/assets/ui/wireframes/workspace-company-type-dropdown-open.png new file mode 100644 index 0000000..dfb9bc0 Binary files /dev/null and b/assets/ui/wireframes/workspace-company-type-dropdown-open.png differ diff --git a/assets/ui/wireframes/workspace-created-success-details.png b/assets/ui/wireframes/workspace-created-success-details.png new file mode 100644 index 0000000..d0f860f Binary files /dev/null and b/assets/ui/wireframes/workspace-created-success-details.png differ diff --git a/assets/ui/wireframes/workspace-created-success-modal.png b/assets/ui/wireframes/workspace-created-success-modal.png new file mode 100644 index 0000000..2329e29 Binary files /dev/null and b/assets/ui/wireframes/workspace-created-success-modal.png differ diff --git a/assets/ui/wireframes/workspace-creation-form-filled-alternative.png b/assets/ui/wireframes/workspace-creation-form-filled-alternative.png new file mode 100644 index 0000000..f247147 Binary files /dev/null and b/assets/ui/wireframes/workspace-creation-form-filled-alternative.png differ diff --git a/assets/ui/wireframes/workspace-creation-form-filled.png b/assets/ui/wireframes/workspace-creation-form-filled.png new file mode 100644 index 0000000..66599f5 Binary files /dev/null and b/assets/ui/wireframes/workspace-creation-form-filled.png differ diff --git a/assets/ui/wireframes/workspace-creation-loading-state.png b/assets/ui/wireframes/workspace-creation-loading-state.png new file mode 100644 index 0000000..634a19e Binary files /dev/null and b/assets/ui/wireframes/workspace-creation-loading-state.png differ diff --git a/assets/ui/wireframes/workspace-creation-modal-empty-fields.png b/assets/ui/wireframes/workspace-creation-modal-empty-fields.png new file mode 100644 index 0000000..818e699 Binary files /dev/null and b/assets/ui/wireframes/workspace-creation-modal-empty-fields.png differ diff --git a/assets/ui/wireframes/workspace-creation-validation-errors.png b/assets/ui/wireframes/workspace-creation-validation-errors.png new file mode 100644 index 0000000..2cf5cb0 Binary files /dev/null and b/assets/ui/wireframes/workspace-creation-validation-errors.png differ diff --git a/assets/ui/wireframes/workspace-dashboard-home.png b/assets/ui/wireframes/workspace-dashboard-home.png new file mode 100644 index 0000000..c02592e Binary files /dev/null and b/assets/ui/wireframes/workspace-dashboard-home.png differ diff --git a/assets/ui/wireframes/workspace-initialization-loading-modal.png b/assets/ui/wireframes/workspace-initialization-loading-modal.png new file mode 100644 index 0000000..df0f31a Binary files /dev/null and b/assets/ui/wireframes/workspace-initialization-loading-modal.png differ diff --git a/assets/ui/wireframes/workspace-settings-company-type-dropdown.png b/assets/ui/wireframes/workspace-settings-company-type-dropdown.png new file mode 100644 index 0000000..4bee038 Binary files /dev/null and b/assets/ui/wireframes/workspace-settings-company-type-dropdown.png differ diff --git a/assets/ui/wireframes/workspace-switcher-menu-open.png b/assets/ui/wireframes/workspace-switcher-menu-open.png new file mode 100644 index 0000000..f6d0b0e Binary files /dev/null and b/assets/ui/wireframes/workspace-switcher-menu-open.png differ diff --git a/assets/ui/wireframes/workspace-team-size-dropdown-open.png b/assets/ui/wireframes/workspace-team-size-dropdown-open.png new file mode 100644 index 0000000..2bc25f6 Binary files /dev/null and b/assets/ui/wireframes/workspace-team-size-dropdown-open.png differ diff --git a/assets/ui/wireframes/workspace-use-case-selection-modal.png b/assets/ui/wireframes/workspace-use-case-selection-modal.png new file mode 100644 index 0000000..e2267cb Binary files /dev/null and b/assets/ui/wireframes/workspace-use-case-selection-modal.png differ diff --git a/assets/ui/wireframes/workspace-use-case-selection-state.png b/assets/ui/wireframes/workspace-use-case-selection-state.png new file mode 100644 index 0000000..5ab102c Binary files /dev/null and b/assets/ui/wireframes/workspace-use-case-selection-state.png differ