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
-
### 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:
| Perfil | Beneficio |
|---|
| Consultoras de Software | Reducción de horas facturables en análisis y toma de requerimientos. |
| Product Managers / Startups | Integració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:
| Problema | Mensaje |
|---|
| El correo ya está registrado en otra cuenta | El correo ingresado ya se encuentra en uso. |
| La contraseña tiene menos de 8 caracteres | La contraseña debe tener al menos 8 caracteres. |
| El formato del correo es inválido | Ingresa 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:
| Situacion | Error |
|---|
| Contraseña incorrecta | Credenciales inválidas. |
| La cuenta aún no ha sido verificada | Debes verificar tu correo antes de iniciar sesión. |
| Demasiados intentos fallidos consecutivos | Cuenta 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:
| Fallo | Error |
|---|
| El archivo es un ejecutable (.exe) | Solo se permiten documentos de texto o PDF. |
| El archivo excede los 50MB | El 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_Inv | Aviso |
|---|
| El correo ya pertenece a la organización | El usuario ya es miembro del equipo. |
| El correo ya tiene una invitación pendiente | Ya 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:
| Condicion | Mensaje_Error |
|---|
| El navegador no tiene permisos de micrófono | Debes otorgar permisos de micrófono para continuar. |
| El usuario tiene un rol sin permisos de creación | No tienes permisos para crear sesiones en este proyecto. |
| El proyecto seleccionado se encuentra archivado | El 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:
| Problema | Mensaje |
|---|
| El campo de motivo está completamente vacío | Debes 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_Invalida | Mensaje_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ñado | El archivo de audio no se puede leer o está dañado. |
| El archivo supera el límite de tamaño máximo permitido | El archivo supera el límite de tamaño permitido. |
| La organización agotó su límite de minutos mensuales | Has 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:
| Escenario | Comportamiento |
|---|
| El contexto del proyecto y el requisito son excesivamente genéricos | No inventa casos de uso sin fundamento ni genera ruido visual. |
| Existen docenas de posibles casos borde asociados al módulo | Filtra 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:
| Falla | Requisito |
|---|
| No seleccionó ni 'Fusionar' ni 'Mantener separada' | Debe elegir explícitamente una acción. |
| Eligió 'Mantener separada' pero dejó la justificación en blanco | La 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_Formulario | Mensaje_Error |
|---|
| El título de la historia se ha borrado por completo | El título de la historia es obligatorio. |
| La descripción quedó completamente vacía | La 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_Integridad | Razon_Rechazo |
|---|
| Faltan definir los criterios de aceptación | La historia debe tener criterios de aceptación antes de ser aprobada. |
| La historia tiene alertas de similitud sin resolver | Debes 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_Fallo | Estado_Final |
|---|
| El usuario deniega los permisos desde la ventana de Jira | Operación cancelada por el usuario, integración inactiva. |
| Fallo de red o timeout durante la comunicación con Jira | Error 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:
| Incumplimiento | Mensaje_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:
| Perfil | Beneficio |
|---|
| Consultoras de Software | Reducción de horas facturables en análisis y toma de requerimientos. |
| Product Managers / Startups | Integració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:
| Problema | Mensaje |
|---|
| El correo ya está registrado en otra cuenta | El correo ingresado ya se encuentra en uso. |
| La contraseña tiene menos de 8 caracteres | La contraseña debe tener al menos 8 caracteres. |
| El formato del correo es inválido | Ingresa 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:
| Situacion | Error |
|---|
| Contraseña incorrecta | Credenciales inválidas. |
| La cuenta aún no ha sido verificada | Debes verificar tu correo antes de iniciar sesión. |
| Demasiados intentos fallidos consecutivos | Cuenta 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:
| Fallo | Error |
|---|
| El archivo es un ejecutable (.exe) | Solo se permiten documentos de texto o PDF. |
| El archivo excede los 50MB | El 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_Inv | Aviso |
|---|
| El correo ya pertenece a la organización | El usuario ya es miembro del equipo. |
| El correo ya tiene una invitación pendiente | Ya 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:
| Condicion | Mensaje_Error |
|---|
| El navegador no tiene permisos de micrófono | Debes otorgar permisos de micrófono para continuar. |
| El usuario tiene un rol sin permisos de creación | No tienes permisos para crear sesiones en este proyecto. |
| El proyecto seleccionado se encuentra archivado | El 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:
| Problema | Mensaje |
|---|
| El campo de motivo está completamente vacío | Debes 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_Invalida | Mensaje_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ñado | El archivo de audio no se puede leer o está dañado. |
| El archivo supera el límite de tamaño máximo permitido | El archivo supera el límite de tamaño permitido. |
| La organización agotó su límite de minutos mensuales | Has 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:
| Escenario | Comportamiento |
|---|
| El contexto del proyecto y el requisito son excesivamente genéricos | No inventa casos de uso sin fundamento ni genera ruido visual. |
| Existen docenas de posibles casos borde asociados al módulo | Filtra 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:
| Falla | Requisito |
|---|
| No seleccionó ni 'Fusionar' ni 'Mantener separada' | Debe elegir explícitamente una acción. |
| Eligió 'Mantener separada' pero dejó la justificación en blanco | La 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_Formulario | Mensaje_Error |
|---|
| El título de la historia se ha borrado por completo | El título de la historia es obligatorio. |
| La descripción quedó completamente vacía | La 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_Integridad | Razon_Rechazo |
|---|
| Faltan definir los criterios de aceptación | La historia debe tener criterios de aceptación antes de ser aprobada. |
| La historia tiene alertas de similitud sin resolver | Debes 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_Fallo | Estado_Final |
|---|
| El usuario deniega los permisos desde la ventana de Jira | Operación cancelada por el usuario, integración inactiva. |
| Fallo de red o timeout durante la comunicación con Jira | Error 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:
| Incumplimiento | Mensaje_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

-### 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

-### 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

-### 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

-## 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:
-
+
**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.
-
+
**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.
-
+
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.
-
+
**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.
+
+
+
+### 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.
+
+
+
+#### 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`.
+
+
+
+## 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.
+
+
+
+### 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.
+
+
+
+#### 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.
+
+
+
+## 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.
+
+
+
+### 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.
+
+
+
+#### 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.
+
+
+
+## 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.
+
+
+
+### 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.
+
+
+
+#### 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.
+
+
+
+## 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.
+
+
+
+### 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`.
+
+
+
+#### 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.
+
+
+
+# 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.
+
+
+
+### 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.
+
+
+
+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.
+
+
+
+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.
+
+
+
+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.
+
+
+
+### 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.
+
+
+
+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.
+
+
+
+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.
+
+
+
+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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+### 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).
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+## 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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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 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 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 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 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 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 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 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.
+
+
+
+**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 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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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 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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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 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.
+
+
+
+**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.
+
+
+
+**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 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 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 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 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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+### 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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+### 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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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 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 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 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 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 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 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 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 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 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 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 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 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.
+
+
+
+**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 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.
+
+
+
+**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.
+
+
+
+**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 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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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 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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+#### 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.
+
+
+
+##### 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.
+
+
+
+#### 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.
+
+
+
+#### 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.
+
+
+
+##### 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.
+
+
+
+#### 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.
+
+
+
+#### 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.
+
+
+
+#### 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.
+
+
+
+#### 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.
+
+
+
+#### 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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+**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.
+
+
+
+## 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.
+
+
+
+**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.
+
+
+
+**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> <