From df9e564cde056e0eb27b65efe8fba3104e16ddff Mon Sep 17 00:00:00 2001 From: Pablo Jimenez Date: Fri, 17 Apr 2026 13:57:01 +0200 Subject: [PATCH 01/13] =?UTF-8?q?A=C3=B1adido=20menu=20de=20specs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/appendix/specs/customers_free.md | 0 docs/appendix/specs/customers_paid.md | 0 docs/appendix/specs/install.md | 0 docs/appendix/specs/intro.md | 0 docs/appendix/specs/loans_free.md | 0 docs/appendix/specs/loans_paid.md | 0 mkdocs.yml | 15 +++++++++++---- 7 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 docs/appendix/specs/customers_free.md create mode 100644 docs/appendix/specs/customers_paid.md create mode 100644 docs/appendix/specs/install.md create mode 100644 docs/appendix/specs/intro.md create mode 100644 docs/appendix/specs/loans_free.md create mode 100644 docs/appendix/specs/loans_paid.md diff --git a/docs/appendix/specs/customers_free.md b/docs/appendix/specs/customers_free.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/appendix/specs/customers_paid.md b/docs/appendix/specs/customers_paid.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/appendix/specs/install.md b/docs/appendix/specs/install.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/appendix/specs/intro.md b/docs/appendix/specs/intro.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/appendix/specs/loans_free.md b/docs/appendix/specs/loans_free.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/appendix/specs/loans_paid.md b/docs/appendix/specs/loans_paid.md new file mode 100644 index 0000000..e69de29 diff --git a/mkdocs.yml b/mkdocs.yml index 814bf4d..52ba182 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,10 +39,13 @@ nav: - 🟢 Nodejs: develop/filtered/nodejs.md - Ahora hazlo tú!: exercise.md - "Anexos": - - "GIT: Tutorial básico": appendix/git.md - - API Rest: appendix/rest.md - - TDD: appendix/tdd.md - - "Spring Data: JPA": appendix/jpa.md + - Spec Driven Development: + - Introducción: appendix/specs/intro.md + - Entorno de desarrollo: appendix/specs/install.md + - 🆓 Gestión de clientes: appendix/specs/customers_free.md + - 🆓 Gestión de préstamos: appendix/specs/loans_free.md + - 💰 Gestión de clientes: appendix/specs/customers_paid.md + - 💰 Gestión de préstamos: appendix/specs/loans_paid.md - Spring Cloud: - Introducción: appendix/springcloud/intro.md - Listado simple: appendix/springcloud/basic.md @@ -57,12 +60,16 @@ nav: - Limpieza: appendix/springbatch/clean.md - Resumen: appendix/springbatch/summary.md - Ahora hazlo tú!: appendix/springbatch/exercise.md + - "Spring Data: JPA": appendix/jpa.md - Dockerízate: - Instalar docker: appendix/docker/installdocker.md - ¿Qué es docker?: appendix/docker/docudocker.md - Practiquemos: appendix/docker/traindocker.md - Resumen: appendix/docker/summary.md - AWS CLI: appendix/aws.md + - "GIT: Tutorial básico": appendix/git.md + - API Rest: appendix/rest.md + - TDD: appendix/tdd.md theme: name: material language: es From d571ae11758347e938d7c27b32bf485887066694 Mon Sep 17 00:00:00 2001 From: Pablo Jimenez Date: Fri, 17 Apr 2026 14:08:21 +0200 Subject: [PATCH 02/13] Estructura base --- docs/appendix/specs/customers_free.md | 0 docs/appendix/specs/customers_paid.md | 0 docs/appendix/specs/install.md | 0 docs/appendix/specs/intro.md | 0 docs/appendix/specs/loans_free.md | 0 docs/appendix/specs/loans_paid.md | 0 docs/specs/customers_free.md | 7 +++++++ docs/specs/customers_paid.md | 5 +++++ docs/specs/install.md | 27 +++++++++++++++++++++++++++ docs/specs/intro.md | 6 ++++++ docs/specs/loans_free.md | 5 +++++ docs/specs/loans_paid.md | 5 +++++ mkdocs.yml | 14 +++++++------- 13 files changed, 62 insertions(+), 7 deletions(-) delete mode 100644 docs/appendix/specs/customers_free.md delete mode 100644 docs/appendix/specs/customers_paid.md delete mode 100644 docs/appendix/specs/install.md delete mode 100644 docs/appendix/specs/intro.md delete mode 100644 docs/appendix/specs/loans_free.md delete mode 100644 docs/appendix/specs/loans_paid.md create mode 100644 docs/specs/customers_free.md create mode 100644 docs/specs/customers_paid.md create mode 100644 docs/specs/install.md create mode 100644 docs/specs/intro.md create mode 100644 docs/specs/loans_free.md create mode 100644 docs/specs/loans_paid.md diff --git a/docs/appendix/specs/customers_free.md b/docs/appendix/specs/customers_free.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/appendix/specs/customers_paid.md b/docs/appendix/specs/customers_paid.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/appendix/specs/install.md b/docs/appendix/specs/install.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/appendix/specs/intro.md b/docs/appendix/specs/intro.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/appendix/specs/loans_free.md b/docs/appendix/specs/loans_free.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/appendix/specs/loans_paid.md b/docs/appendix/specs/loans_paid.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/specs/customers_free.md b/docs/specs/customers_free.md new file mode 100644 index 0000000..13e886f --- /dev/null +++ b/docs/specs/customers_free.md @@ -0,0 +1,7 @@ +Dividir las specs en dos proposes (uno para back y uno para front) + +Hacer todo el back primero y luego todo el front + +Un último punto de probar a levantar y probar la app --> además pueden comparar con su código del ejercicio + + diff --git a/docs/specs/customers_paid.md b/docs/specs/customers_paid.md new file mode 100644 index 0000000..1cb2b47 --- /dev/null +++ b/docs/specs/customers_paid.md @@ -0,0 +1,5 @@ +Dividir las specs en dos proposes (uno para back y uno para front) + +Hacer todo el back primero y luego todo el front + +Un último punto de probar a levantar y probar la app --> además pueden comparar con su código del ejercicio \ No newline at end of file diff --git a/docs/specs/install.md b/docs/specs/install.md new file mode 100644 index 0000000..2708db8 --- /dev/null +++ b/docs/specs/install.md @@ -0,0 +1,27 @@ +Explicar lo que tenemos que tener instalado + +VisualStudioCode +Node +Openspec + + +Luego explicar que vamos a trabajar desde VisualStudio code y por tanto necesitamos un directorio con: + +- backend +- frontend +- specs + +Los proyectos base se deben descargar de aquí --> https://github.com/ccsw-csd/tutorial-proyectos + + +para el ejemplo vamos a usar angular y springboot + + +Luego contar que hay modelos de pago y modelos gratuitos y por tanto deben elegir una de las opciones. Contar beneficios de uno y de otro + + +- 🆓 Gestión de clientes: specs/customers_free.md +- 🆓 Gestión de préstamos: specs/loans_free.md + +- 💰 Gestión de clientes: specs/customers_paid.md +- 💰 Gestión de préstamos: specs/loans_paid.md diff --git a/docs/specs/intro.md b/docs/specs/intro.md new file mode 100644 index 0000000..ea67c53 --- /dev/null +++ b/docs/specs/intro.md @@ -0,0 +1,6 @@ +Primero contar que vamos a hacer una implementación alternativa utilizado Spec Drive Development (la intro del word) + +Luego explicar en que consiste SDD (muy por encima un párrafo o dos sacado de copilot) + +Luego la metodología OpenSpec (que ya está en el word) + diff --git a/docs/specs/loans_free.md b/docs/specs/loans_free.md new file mode 100644 index 0000000..1cb2b47 --- /dev/null +++ b/docs/specs/loans_free.md @@ -0,0 +1,5 @@ +Dividir las specs en dos proposes (uno para back y uno para front) + +Hacer todo el back primero y luego todo el front + +Un último punto de probar a levantar y probar la app --> además pueden comparar con su código del ejercicio \ No newline at end of file diff --git a/docs/specs/loans_paid.md b/docs/specs/loans_paid.md new file mode 100644 index 0000000..1cb2b47 --- /dev/null +++ b/docs/specs/loans_paid.md @@ -0,0 +1,5 @@ +Dividir las specs en dos proposes (uno para back y uno para front) + +Hacer todo el back primero y luego todo el front + +Un último punto de probar a levantar y probar la app --> además pueden comparar con su código del ejercicio \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 52ba182..58c971b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,14 +38,14 @@ nav: - 🟢 Spring Boot: develop/filtered/springboot.md - 🟢 Nodejs: develop/filtered/nodejs.md - Ahora hazlo tú!: exercise.md + - Spec Driven Development: + - Introducción: specs/intro.md + - Entorno de desarrollo: specs/install.md + - 🆓 Gestión de clientes: specs/customers_free.md + - 🆓 Gestión de préstamos: specs/loans_free.md + - 💰 Gestión de clientes: specs/customers_paid.md + - 💰 Gestión de préstamos: specs/loans_paid.md - "Anexos": - - Spec Driven Development: - - Introducción: appendix/specs/intro.md - - Entorno de desarrollo: appendix/specs/install.md - - 🆓 Gestión de clientes: appendix/specs/customers_free.md - - 🆓 Gestión de préstamos: appendix/specs/loans_free.md - - 💰 Gestión de clientes: appendix/specs/customers_paid.md - - 💰 Gestión de préstamos: appendix/specs/loans_paid.md - Spring Cloud: - Introducción: appendix/springcloud/intro.md - Listado simple: appendix/springcloud/basic.md From 706ed6a48f69b04f2e8bded97425759667db5126 Mon Sep 17 00:00:00 2001 From: Pablo Jimenez Date: Mon, 20 Apr 2026 14:39:40 +0200 Subject: [PATCH 03/13] refactor --- docs/specs/customers_free.md | 28 +++++++++++++++++++++++++--- docs/specs/customers_paid.md | 6 ++++++ docs/specs/install.md | 6 ++++++ docs/specs/intro.md | 7 +++++++ docs/specs/loans_free.md | 6 ++++++ docs/specs/loans_paid.md | 6 ++++++ mkdocs.yml | 2 +- 7 files changed, 57 insertions(+), 4 deletions(-) diff --git a/docs/specs/customers_free.md b/docs/specs/customers_free.md index 13e886f..18fbabf 100644 --- a/docs/specs/customers_free.md +++ b/docs/specs/customers_free.md @@ -1,7 +1,29 @@ -Dividir las specs en dos proposes (uno para back y uno para front) +# Gestión de clientes (modelo gratuito) -Hacer todo el back primero y luego todo el front +!!! warning Atención + Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente -Un último punto de probar a levantar y probar la app --> además pueden comparar con su código del ejercicio + +## Punto de partida + +Si has llegado hasta aquí entiendo que has leído la introducción de lo que vamos a hacer y la instalación del entorno. + +Recuerda que partiremos del tutorial en el punto inicial del ejercicio "Ahora hazlo tú!". Si no tienes el código fuente en dicho punto te lo puedes [descargar de aquí](https://github.com/ccsw-csd/tutorial-proyectos), y coger el backend y el frontend que quieras para hacer el ejercicio. + +Nosotros con fines didacticos partiremos del tutorial de "server-springboot" y "client-angular17". + + +Crearemos un directorio general que contendrá las dos carpetas, por comodidad durante el tutorial las llamaremos + + + + +### Guión + +- Dividir las specs en dos proposes (uno para back y uno para front) + +- Hacer todo el back primero y luego todo el front + +- Un último punto de probar a levantar y probar la app --> además pueden comparar con su código del ejercicio diff --git a/docs/specs/customers_paid.md b/docs/specs/customers_paid.md index 1cb2b47..8bd5b13 100644 --- a/docs/specs/customers_paid.md +++ b/docs/specs/customers_paid.md @@ -1,3 +1,9 @@ +# Gestión de clientes (modelo con licencia) + +!!! warning Atención + Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente + + Dividir las specs en dos proposes (uno para back y uno para front) Hacer todo el back primero y luego todo el front diff --git a/docs/specs/install.md b/docs/specs/install.md index 2708db8..d1960b7 100644 --- a/docs/specs/install.md +++ b/docs/specs/install.md @@ -1,3 +1,9 @@ +# Instalación del entorno + +!!! warning Atención + Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente + + Explicar lo que tenemos que tener instalado VisualStudioCode diff --git a/docs/specs/intro.md b/docs/specs/intro.md index ea67c53..930eaaf 100644 --- a/docs/specs/intro.md +++ b/docs/specs/intro.md @@ -1,3 +1,10 @@ +# Introducción a Spec-Driven Development + +!!! warning Atención + Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente + + + Primero contar que vamos a hacer una implementación alternativa utilizado Spec Drive Development (la intro del word) Luego explicar en que consiste SDD (muy por encima un párrafo o dos sacado de copilot) diff --git a/docs/specs/loans_free.md b/docs/specs/loans_free.md index 1cb2b47..e61c3c4 100644 --- a/docs/specs/loans_free.md +++ b/docs/specs/loans_free.md @@ -1,3 +1,9 @@ +# Gestión de préstamos (modelo gratuito) + +!!! warning Atención + Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente + + Dividir las specs en dos proposes (uno para back y uno para front) Hacer todo el back primero y luego todo el front diff --git a/docs/specs/loans_paid.md b/docs/specs/loans_paid.md index 1cb2b47..d1365d8 100644 --- a/docs/specs/loans_paid.md +++ b/docs/specs/loans_paid.md @@ -1,3 +1,9 @@ +# Gestión de préstamos (modelo con licencia) + +!!! warning Atención + Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente + + Dividir las specs en dos proposes (uno para back y uno para front) Hacer todo el back primero y luego todo el front diff --git a/mkdocs.yml b/mkdocs.yml index 58c971b..f01bc6d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,7 +40,7 @@ nav: - Ahora hazlo tú!: exercise.md - Spec Driven Development: - Introducción: specs/intro.md - - Entorno de desarrollo: specs/install.md + - Instalación entorno: specs/install.md - 🆓 Gestión de clientes: specs/customers_free.md - 🆓 Gestión de préstamos: specs/loans_free.md - 💰 Gestión de clientes: specs/customers_paid.md From ce6e9e47a86e1dab669d40a8510d9b29d5c7a22c Mon Sep 17 00:00:00 2001 From: Pablo Jimenez Date: Mon, 20 Apr 2026 14:50:17 +0200 Subject: [PATCH 04/13] Intro de cliente free --- docs/assets/images/specs-install_1.png | Bin 0 -> 25990 bytes docs/assets/images/specs-install_2.png | Bin 0 -> 31183 bytes docs/specs/customers_free.md | 17 ++++++++++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 docs/assets/images/specs-install_1.png create mode 100644 docs/assets/images/specs-install_2.png diff --git a/docs/assets/images/specs-install_1.png b/docs/assets/images/specs-install_1.png new file mode 100644 index 0000000000000000000000000000000000000000..1e1b8af4777a062587c3798314fa399b854f6d7e GIT binary patch literal 25990 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 IC7=5L2a^q-z5oCK literal 0 HcmV?d00001 diff --git a/docs/assets/images/specs-install_2.png b/docs/assets/images/specs-install_2.png new file mode 100644 index 0000000000000000000000000000000000000000..98bdfedba263fe19961386b6a327ff4e93c2e2b6 GIT binary patch literal 31183 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 PhLV$dBUvGC=>NX}#=n9r literal 0 HcmV?d00001 diff --git a/docs/specs/customers_free.md b/docs/specs/customers_free.md index 18fbabf..7bb1803 100644 --- a/docs/specs/customers_free.md +++ b/docs/specs/customers_free.md @@ -13,7 +13,22 @@ Recuerda que partiremos del tutorial en el punto inicial del ejercicio "Ahora ha Nosotros con fines didacticos partiremos del tutorial de "server-springboot" y "client-angular17". -Crearemos un directorio general que contendrá las dos carpetas, por comodidad durante el tutorial las llamaremos +Crearemos un directorio general que contendrá las dos carpetas, por comodidad durante el tutorial las llamaremos **```backend```** y **```frontend```**. Debería quedar algo así: + +![workspace](../assets/images/specs-install_1.png) + +Desde consola o desde un terminal, nos situaremos en ese directorio raiz y lanzaremos el inicializador de open specs. Escribiremos el siguiente comando: + +``` +openspec init +``` + +Elegiremos de la lista que nos aparece ```GitHub Copilot```, pulsaremos **```Enter```** para añadirlo y luego **```Tab```** para validar la selección. + +![workspace](../assets/images/specs-install_2.png) + + +Esto nos instalará las plantillas necesarias para usar open specs con GitHub Copilot. From b75c824eb4504628ddbe93fa4f0b8ca0c55ec1a9 Mon Sep 17 00:00:00 2001 From: Pablo Jimenez Date: Tue, 21 Apr 2026 08:44:53 +0200 Subject: [PATCH 05/13] Cerrado backend primer ejercicio --- docs/assets/images/specs-customer-free_1.png | Bin 0 -> 27942 bytes docs/assets/images/specs-customer-free_2.png | Bin 0 -> 18540 bytes docs/assets/images/specs-customer-free_3.png | Bin 0 -> 4870 bytes docs/specs/customers_free.md | 463 ++++++++++++++++++- 4 files changed, 450 insertions(+), 13 deletions(-) create mode 100644 docs/assets/images/specs-customer-free_1.png create mode 100644 docs/assets/images/specs-customer-free_2.png create mode 100644 docs/assets/images/specs-customer-free_3.png diff --git a/docs/assets/images/specs-customer-free_1.png b/docs/assets/images/specs-customer-free_1.png new file mode 100644 index 0000000000000000000000000000000000000000..6c8067e953eafe02d952235b23930d4644545ee5 GIT binary patch literal 27942 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�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 literal 0 HcmV?d00001 diff --git a/docs/assets/images/specs-customer-paid_4.png b/docs/assets/images/specs-customer-paid_4.png new file mode 100644 index 0000000000000000000000000000000000000000..8fcabc44917eac944eb78c44f8680952a3bac635 GIT binary patch literal 7853 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 además pueden comparar con su código del ejercicio \ No newline at end of file +Una vez descargado, elige el **backend** y el **frontend** que prefieras. En este tutorial, por motivos didácticos, yo utilizaré: + +- ``server-springboot`` +- ``client-angular17`` + +## Estructura inicial del proyecto + +Crearemos un directorio general que contendrá ambos proyectos. Para simplificar el tutorial, durante todo el documento los llamaremos: + +- **``backend``** +- **``frontend``** + +La estructura debería ser similar a esta: + +![workspace](../assets/images/specs-install_1.png) + +Desde consola (o desde el terminal de tu IDE), nos situamos en el **directorio raíz** y lanzamos el inicializador de Open Spec. + +``` +openspec init +``` + +Seleccionamos **``GitHub Copilot``**, pulsamos **``Enter``** para añadirlo y después **``Tab``** para validar la selección. + +![workspace](../assets/images/specs-install_2.png) + +Esto instalará las plantillas necesarias para poder trabajar con **Open Spec + GitHub Copilot**. + +## Consejos antes de empezar + +Vamos a trabajar con un **modelo de pago**, por lo que es importante entender qué ventajas nos ofrece frente al modelo gratuito. + +En este caso podremos: + +- Trabajar con **mayor contexto** +- Analizar **frontend y backend simultáneamente** +- Reducir la fragmentación de tareas +- Obtener propuestas e implementaciones más completas y robustas + +El modelo que utilizaremos será **``Claude Sonnet 4.6``**. Para ello debes: + +1. Tener una **cuenta premium con acceso a modelos de pago** +2. Hacer login con tu cuenta de GitHub +3. Activar GitHub Copilot +4. En el chat, abrir el tercer desplegable +5. Seleccionar el modelo **Claude Sonnet 4.6** + +![Claude Sonnet 4.6](../assets/images/specs-customer-paid_1.png) + +En cualquier momento puedes ver el consumo mensual de tu cuenta pulsando el icono de la rana 🐸 en la esquina inferior derecha. El contador **se reinicia cada mes**. + +## Estrategia de trabajo + +Vamos a abordar el ejercicio como un **único bloque de trabajo**, analizando y construyendo la funcionalidad de forma **simultánea en backend y frontend**. + +De esta manera aprovechamos el **mayor contexto** del modelo de pago, permitiendo: + +1. Analizar **``backend`` y ``frontend`` al mismo tiempo** +2. Diseñar la funcionalidad de forma coherente en ambas capas desde el inicio + +Esto nos permite mantener una visión global del sistema durante todo el proceso y reducir la necesidad de dividir artificialmente el trabajo en fases independientes por capa. + +Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ti te genera algo diferente a lo que ves aquí, probablemente seguirá siendo válido. No te frustres y ajusta los prompts si es necesario. + +## Desarrollo de la funcionalidad + +Seguiremos el ciclo completo de Open Spec: + +``` +1. Explore +2. Propose +3. Apply +4. Archive +``` + +### Explore + +El objetivo de esta fase es **analizar el sistema existente**, sin modificar nada. + +Buscamos: + +- Entender la estructura actual de la aplicación +- Identificar patrones y estructuras reutilizables +- Comprender cómo se comunican frontend y backend + +Aspectos a revisar: + +**Organización por dominios** + +- Cómo están estructurados los dominios existentes (category, author, game…) +- Qué carpetas existen en frontend y backend +- Cómo se relacionan los dominios entre capas + +**Angular** + +- Componentes +- Servicios +- Modelos +- Routing + +**Spring Boot** + +- Controller +- Service +- Repository +- Entity +- DTO + +**Patrón CRUD** + +- Cómo se implementan los listados +- Cómo se implementan las operaciones de creación, edición y borrado +- Cómo funcionan las ventanas de creación y edición (modales) + +**Conexión frontend-backend** + +- Cómo Angular llama a los endpoints +- Cómo se construyen las URLs +- Qué DTOs se utilizan en la comunicación + +**Reutilización** + +- Código común entre dominios +- Patrones repetidos +- Estructuras compartidas entre diferentes funcionalidades + +⚠️ En esta fase: + +- **NO** se escribe código +- **NO** se diseña la solución +- **NO** se inventan estructuras nuevas + +Solo se analiza el **sistema actual**. + +--- + +**📜 Prompt** + +Lo que haremos será escribir en el chat de ``Visual Studio Code`` el comando y las instrucciones que queramos darle. ``Recuerda haber elegido Claude Sonnet 4.6 y estar trabajando en modo Agent``. + +En este caso, hemos añadido las carpetas del proyecto **``frontend`` y ``backend`` al contexto**, por lo que el análisis se realizará sobre **el sistema completo**. + +Para ello, desde el propio Chat de Copilot, pulsando el botón **“+”**, puedes seleccionar y añadir tanto **archivos individuales** como **directorios completos** del proyecto. También es posible añadirlos **arrastrándolos directamente al chat**. + +![Ventana de chat](../assets/images/specs-customer-paid_2.png) + +``` +/opsx:explore + +Analiza el proyecto actual (Angular 17 + Spring Boot) que se ha añadido al contexto. Una vez analizado, responde: + +1. ¿Cómo están implementados los CRUD existentes? +- Backend: Controller, Service, Repository +- Frontend: componentes, servicios y modelo + +2. ¿Qué estructura siguen los dominios en backend y en frontend? + +3. ¿Cómo se implementan las operaciones? +- Listado +- Creación/edición +- Borrado +- Cómo funcionan las ventanas de creación y edición (modales) + +4. ¿Cómo se comunican frontend y backend? +- Servicios Angular +- Construcción de URLs + +5. ¿Qué patrones o estructuras comunes se repiten en los CRUD existentes? +- Clases reutilizables +- Lógica repetida +- Estructuras comunes entre dominios + +NO propongas soluciones. +NO diseñes nuevas funcionalidades. +Solo analiza el sistema actual. +``` + +Este comando realizará un análisis exhaustivo de tu sistema que servirá como base para definir la nueva funcionalidad en la siguiente fase. + +!!! tip "Sobre los permisos" + Es posible que durante el análisis te pida permiso para hacer ciertas tareas. Le puedes ir dando permiso una a una o darle permiso en todo el workspace, eso lo dejamos a tu elección. + +En cualquier momento puedes ver el consumo de la ventana de contexto para saber si todo el conocimiento del sistema está en memoria o no. En el icono de la gráfica circular que está situada en la parte inferior derecha del chat. + +![Ventana contexto](../assets/images/specs-customer-paid_3.png) + +### Propose + +Una vez analizado el sistema en la fase Explore, el siguiente paso es definir de forma clara y estructurada **la nueva funcionalidad a implementar**. + +En esta fase establecemos **qué vamos a construir**, basándonos estrictamente en el resultado del Explore y aprovechando que el modelo dispone de una **visión completa del sistema** (frontend y backend). + +Esta fase actúa como puente entre el análisis y la implementación, permitiendo diseñar la solución antes de escribir código y reduciendo el riesgo de errores durante el desarrollo. + +Durante esta fase debes especificar: + +**Descripción funcional** + +- Qué hace la funcionalidad +- Qué problema resuelve + +**Reglas de negocio** + +- Validaciones +- Restricciones +- Comportamientos esperados + +**Diseño backend** + +- Endpoints necesarios +- Estructura del dominio (Entity, DTO, Service, Repository) +- Tipo de operaciones (listado, creación, edición, borrado) + +**Diseño frontend** + +- Componentes necesarios +- Flujo de usuario (listado, abrir modal, guardar, borrar) +- Servicios Angular + +**Decisiones técnicas** + +- Qué patrones existentes se reutilizan +- Qué se mantiene igual que en otros dominios +- Qué diferencias introduce esta funcionalidad + +**Plan de implementación** + +- Tareas ordenadas +- Separación backend / frontend + +Aquí dejamos claro: + +- Qué funcionalidad se va a añadir +- Qué reglas de negocio existen +- Qué piezas del sistema se ven afectadas +- Qué tareas habrá que ejecutar + +⚠️ En esta fase: + +- **NO** se implementa código +- **NO** se redefine el sistema + +**📜 Prompt** + +Recuerda que seguimos trabajando en **modo Agent**, con las carpetas del proyecto **``frontend`` y ``backend`` añadidas al contexto**. + +Para nuestro ejemplo, lo que haremos será escribir en el chat de ``Visual Studio Code`` el siguiente prompt: + +``` +/opsx:propose client + +Define la funcionalidad de gestión de clientes basándote en el sistema actual (Angular 17 + Spring Boot) y en los patrones identificados en la fase Explore. + +Requisitos funcionales: +- Se necesita un CRUD de clientes +- Un cliente solo tiene: id, name +- El listado será simple, sin filtros ni paginación +- Existirá un formulario de alta / edición en modal +- El único campo editable será el nombre +- No se puede crear un cliente con un nombre ya existente (validación obligatoria) + +Define: + +1. Descripción de la funcionalidad + +2. Reglas de negocio + +3. Diseño backend: +- Endpoints necesarios +- Estructura del dominio (Entity, DTO, Service, Repository) + +4. Diseño frontend: +- Componentes necesarios +- Flujo de interacción (listado, abrir modal, guardar, borrar) + +5. Decisiones técnicas: +- Qué patrones del sistema actual se reutilizan + +NO implementes código. +NO analices de nuevo el proyecto. +Basa la propuesta en los patrones detectados en la fase Explore. +``` + +Este comando debería generar un directorio dentro de ``changes`` con el nombre que le hayamos puesto a la propuesta y dentro los 4 ficheros solicitados: + +![proposal](../assets/images/specs-customer-paid_4.png) + +Además en el chat también hará un pequeño resumen de lo que ha propuesto como cambios. + +Veamos lo que contiene cada uno de esos ficheros. + +**📄 proposal.md** + +Define la funcionalidad a alto nivel. + +Incluye: + +- El problema que se quiere resolver (Why) +- Qué cambios se van a introducir (What Changes) +- El alcance funcional +- El impacto en la aplicación + +Responde a: ¿Qué se va a construir y por qué? + +**📄 design.md** + +Describe el diseño técnico de la solución. + +Incluye: + +- Contexto del sistema actual +- Objetivos (Goals / Non-Goals) +- Decisiones técnicas y su justificación +- Alternativas consideradas +- Riesgos y trade-offs + +Responde a: ¿Cómo se va a construir y por qué se ha elegido este enfoque? + +**📄 spec.md** + +Define el comportamiento funcional esperado. + +Incluye: + +- Requisitos funcionales +- Casos de uso expresados como escenarios (WHEN / THEN) +- Reglas de negocio +- Validaciones y restricciones + +Responde a: ¿Qué debe hacer el sistema? + +**📄 tasks.md** + +Descompone la implementación en tareas ejecutables. Quizá es el fichero más importante. + +Incluye: + +- Lista ordenada de tareas +- Pasos concretos para implementar la funcionalidad + +Responde a: ¿Cómo se implementa paso a paso? + +**Relación entre los artefactos** + +Cada uno de los ficheros generados cumple un rol específico dentro del flujo de Open Spec: + +- **spec.md** → define el comportamiento esperado (*qué debe hacer el sistema*) +- **design.md** → define la solución técnica (*cómo se va a construir*) +- **proposal.md** → aporta contexto y alcance (*por qué se construye*) +- **tasks.md** → guía la ejecución paso a paso (*cómo se implementa*) + +Esta separación de responsabilidades permite: + +- Evitar mezclar requisitos con implementación +- Revisar cada nivel de forma independiente +- Detectar errores e inconsistencias antes de escribir código + +Estos artefactos constituyen la base para la siguiente fase: **Apply**, donde se ejecutará la implementación siguiendo las tareas definidas. + +!!! tip "Responsabilidades como developer IA" + En este punto la IA te ha hecho una propuesta que puede ser correcta o no, recordemos que se trata de un modelo matemático-probabilístico. Si hay algo de lo propuesto que no te encaja o es erróneo deberías comentarlo mediante el chat o corregirlo de forma manual en el fichero que corresponda. Por ejemplo si quieres añadir una tarea porqué se te ha olvidado incluirla en el prompt original, deberías decirle al modelo que te incluya la nueva tarea. + +Una vez estemos de acuerdo con la propuesta que nos ha hecho la IA, podemos pasar al siguiente punto. + +### Apply + +Una vez validada la propuesta, ejecutamos la implementación: + +El objetivo de esta fase es transformar los artefactos generados +(`proposal.md`, `design.md`, `spec.md`, `tasks.md`) en **código funcional**, asegurando que: + +- Se respetan los requisitos funcionales definidos en `spec.md` +- Se siguen las decisiones técnicas establecidas en `design.md` +- Se ejecutan las tareas en el orden definido en `tasks.md` + +**📜 Prompt** + +Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguiente prompt: + +``` +/opsx:apply +``` + +El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado tanto en la carpeta ``backend`` como en la carpeta ``frontend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. + +## Pruebas + +Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. + +Arranca el **backend** y el **frontend** y verifica: + +- La aplicación levanta correctamente +- Las nuevas funcionalidades añadidas están accesibles +- Los flujos principales definidos en `spec.md` funcionan como se espera + +!!! warning "Ojo no te fies" + Ojo no te fies de todo lo que construya la IA. Tu estás al mando, tu debes decidir si el sistema está correctamente implementado o no. Es tu responsabilidad. + +Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, es el momento de escribirlo por el chat indicándole exactamente que es lo que falta. Cuanto más preciso y conciso seas, mejor implementará la IA. + + +### Archive +Y llegamos a la última etapa que nos define Open Spec, el último paso es archivar el cambio. + +El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. + +En esta fase se asegura que: + +- La funcionalidad ha sido correctamente implementada y validada +- No existen incidencias críticas pendientes +- La documentación asociada al cambio está completa y actualizada +- Existe una trazabilidad entre requisitos, diseño e implementación + +Aunque parezca mentira, este paso es muy importante ya que nos servirá para actualizar el contexto del sistema y archivar todos los cambios para futuras consultas. + + +**📜 Prompt** + +De nuevo nos vamos al chat de ``Visual Studio Code`` el siguiente prompt: + +``` +/opsx:archive +``` + +En ese caso, el sistema solicita confirmación para sincronizar los requisitos antes de archivar el cambio. + +**¿Qué significa sincronizar?** + +Al seleccionar la opción de sincronización: + +- Se integran los nuevos requisitos definidos en spec.md +- Se crea o actualiza el spec definitivo +- Los requisitos pasan a formar parte oficial del sistema + +Es decir, los requisitos pasan de ser un cambio temporal a formar parte permanente del sistema. + +**¿Qué ocurre si no se sincroniza?** + +Si se decide no sincronizar: + +- El código permanece implementado +- Los requisitos no se registran en los specs principales + +Esto puede provocar: + +- Pérdida de trazabilidad +- Dificultad para futuras evoluciones +- Desalineación entre código y documentación + +**Tras completar el proceso de Archive:** + +- La funcionalidad queda documentada como completada +- El cambio deja de formar parte de los cambios activos +- Los requisitos quedan integrados definitivamente en el sistema (si se ha sincronizado) \ No newline at end of file From 5e89313f335a24cc55980bf00c046ffbd2c63c27 Mon Sep 17 00:00:00 2001 From: lauramgll <156073035+lauramgll@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:02:38 +0200 Subject: [PATCH 08/13] =?UTF-8?q?Open=20Spec=20-=20Ejercicio=20pr=C3=A9sta?= =?UTF-8?q?mos=20(modelo=20pago)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/specs/customers_free.md | 18 +- docs/specs/customers_paid.md | 11 +- docs/specs/loans_paid.md | 428 ++++++++++++++++++++++++++++++++++- 3 files changed, 450 insertions(+), 7 deletions(-) diff --git a/docs/specs/customers_free.md b/docs/specs/customers_free.md index 4d19011..24e41a3 100644 --- a/docs/specs/customers_free.md +++ b/docs/specs/customers_free.md @@ -146,6 +146,8 @@ Aspectos a revisar: Solo se analiza el **sistema actual**. +--- + **📜 Prompt** Lo que haremos será escribir en el chat de ``Visual Studio Code`` el comando y las instrucciones que queramos darle. ``Recuerda haber elegido Claude Haiku``. @@ -250,6 +252,8 @@ Aquí dejamos claro: - **NO** se implementa código - **NO** se redefine el sistema +--- + **📜 Prompt** Para nuestro ejemplo, lo que haremos será escribir en el chat de ``Visual Studio Code`` el siguiente prompt: @@ -408,6 +412,8 @@ El objetivo de esta fase es transformar los artefactos generados - Se siguen las decisiones técnicas establecidas en `design.md` - Se ejecutan las tareas en el orden definido en `tasks.md` +--- + **📜 Prompt** Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguiente prompt: @@ -438,7 +444,8 @@ Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, e ### Archive -Y llegamos a la última etapa que nos define Open Spec, el último paso es archivar el cambio. + +Y llegamos a la última etapa que nos define Open Spec, donde se archiva el cambio y se da por finalizada la funcionalidad. El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. @@ -451,6 +458,7 @@ En esta fase se asegura que: Aunque parezca mentira, este paso es muy importante ya que nos servirá para actualizar el contexto del sistema y archivar todos los cambios para futuras consultas. +--- **📜 Prompt** @@ -521,6 +529,8 @@ Bueno, pues ahora que ya tenemos el backend implementado, realizaremos de nuevo De nuevo el objetivo de esta fase es analizar el sistema existente, sin modificar nada, pero esta vez nos centraremos en el frontend. +--- + **📜 Prompt** Vamos al chat de ``Visual Studio Code`` y escribimos el comando: @@ -568,6 +578,8 @@ Este comando realizará un análisis exhaustivo de tu sistema y lo dejará escri Una vez definido el análisis inicial, lo siguiente es pedirle una propuesta de lo que queremos construir. +--- + **📜 Prompt** De nuevo en el chat de ``Visual Studio Code`` escribimos el comando: @@ -641,6 +653,8 @@ Puntos a destacar de este prompt: Cuando estemos de acuerdo con la propuesta que nos ha hecho la IA y sobre todo con las tasks que propone realizar, lanzamos la fase de implementación. +--- + **📜 Prompt** Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguiente prompt: @@ -665,6 +679,8 @@ Si algo no encaja, es buen momento para conversarlo con la IA y que realice los Una vez tengamos todo funcionando y perfectamente implementado, pasamos a la última etapa para archivar y sincronizar nuestro cambio. +--- + **📜 Prompt** De nuevo nos vamos al chat de ``Visual Studio Code`` el siguiente prompt: diff --git a/docs/specs/customers_paid.md b/docs/specs/customers_paid.md index 935b6f2..19317ed 100644 --- a/docs/specs/customers_paid.md +++ b/docs/specs/customers_paid.md @@ -253,6 +253,8 @@ Aquí dejamos claro: - **NO** se implementa código - **NO** se redefine el sistema +--- + **📜 Prompt** Recuerda que seguimos trabajando en **modo Agent**, con las carpetas del proyecto **``frontend`` y ``backend`` añadidas al contexto**. @@ -294,7 +296,8 @@ NO analices de nuevo el proyecto. Basa la propuesta en los patrones detectados en la fase Explore. ``` -Este comando debería generar un directorio dentro de ``changes`` con el nombre que le hayamos puesto a la propuesta y dentro los 4 ficheros solicitados: +Este comando debería generar un directorio dentro de ``changes`` con el nombre que le hayamos puesto a la propuesta y dentro los siguientes ficheros: +`proposal.md`, `design.md`, `spec.md`, `tasks.md` ![proposal](../assets/images/specs-customer-paid_4.png) @@ -386,6 +389,8 @@ El objetivo de esta fase es transformar los artefactos generados - Se siguen las decisiones técnicas establecidas en `design.md` - Se ejecutan las tareas en el orden definido en `tasks.md` +--- + **📜 Prompt** Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguiente prompt: @@ -413,7 +418,8 @@ Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, e ### Archive -Y llegamos a la última etapa que nos define Open Spec, el último paso es archivar el cambio. + +Y llegamos a la última etapa que nos define Open Spec, donde se archiva el cambio y se da por finalizada la funcionalidad. El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. @@ -426,6 +432,7 @@ En esta fase se asegura que: Aunque parezca mentira, este paso es muy importante ya que nos servirá para actualizar el contexto del sistema y archivar todos los cambios para futuras consultas. +--- **📜 Prompt** diff --git a/docs/specs/loans_paid.md b/docs/specs/loans_paid.md index d1365d8..750cc51 100644 --- a/docs/specs/loans_paid.md +++ b/docs/specs/loans_paid.md @@ -1,11 +1,431 @@ # Gestión de préstamos (modelo con licencia) !!! warning Atención - Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente + Esta sección se encuentra en desarrollo 🚧. + **NO se recomienda realizarla** a menos que te lo hayan indicado expresamente. +## Punto de partida -Dividir las specs en dos proposes (uno para back y uno para front) +Si has llegado hasta aquí, entiendo que ya has completado la funcionalidad de **gestión de clientes** utilizando el modelo con licencia. -Hacer todo el back primero y luego todo el front +A partir de ahora vamos a dar por hecho que partimos de ese estado del sistema, donde: -Un último punto de probar a levantar y probar la app --> además pueden comparar con su código del ejercicio \ No newline at end of file +- Existe un CRUD funcional de clientes +- La funcionalidad está implementada, validada y archivada +- Los patrones de backend y frontend introducidos ya forman parte del sistema + +Una vez llegados a este punto, asumimos que el proyecto **ya está descargado y configurado**, y que hemos trabajado previamente sobre la funcionalidad de **gestión de clientes**. + +Por tanto, **continuaremos utilizando los mismos proyectos y directorios**, sin realizar ninguna instalación ni configuración adicional. + +En este tutorial seguiremos trabajando sobre: + +- ``server-springboot`` como **``backend``** +- ``client-angular17`` como **``frontend``** + +## Consejos antes de empezar + +Continuaremos trabajando con un **modelo de pago**, utilizando **``Claude Sonnet 4.6``** y el mismo workspace que en la funcionalidad de **gestión de clientes**. + +Antes de comenzar, ten en cuenta lo siguiente: + +- Para cada **nueva funcionalidad**, es recomendable iniciar una **nueva conversación de chat** dentro del mismo proyecto + +Esto ayuda a mantener el contexto limpio y a que el modelo se centre exclusivamente en la funcionalidad que vamos a abordar. + +Recuerda que en cualquier momento puedes ver el consumo mensual de tu cuenta pulsando el icono de la rana 🐸 en la esquina inferior derecha. El contador **se reinicia cada mes**. + +## Estrategia de trabajo + +Vamos a abordar el ejercicio como un **único bloque de trabajo**, analizando y construyendo la funcionalidad de forma **simultánea en backend y frontend**. + +De esta manera aprovechamos el **mayor contexto** del modelo de pago, permitiendo: + +1. Analizar **``backend`` y ``frontend`` al mismo tiempo** +2. Diseñar la funcionalidad de forma coherente en ambas capas desde el inicio + +Esto nos permite mantener una visión global del sistema durante todo el proceso y reducir la necesidad de dividir artificialmente el trabajo en fases independientes por capa. + +Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ti te genera algo diferente a lo que ves aquí, probablemente seguirá siendo válido. No te frustres y ajusta los prompts si es necesario. + +## Desarrollo de la funcionalidad + +Seguiremos el ciclo completo de Open Spec: + +``` +1. Explore +2. Propose +3. Apply +4. Archive +``` + +### Explore + +El objetivo de esta fase es **analizar el sistema existente**, sin modificar nada. + +A diferencia de la gestión de clientes, este caso de uso introduce una mayor complejidad, principalmente por: + +- Relaciones entre entidades (cliente, juego, préstamo) +- Uso de **paginación** en los listados +- Aplicación de **filtros combinados** +- Necesidad de **validaciones de negocio más complejas** + +En esta fase se analizará qué partes del sistema actual ya resuelven este tipo de problemas y pueden reutilizarse, y qué aspectos no están implementados y deberán abordarse en fases posteriores. + +Aspectos a revisar: + +**Paginación** + +- Cómo se implementa en backend (uso de Page) +- Cómo se consume en frontend +- Cómo se integra en tablas + +**Filtros** + +- Cómo se implementa en el catálogo de juegos +- Cómo se implementan filtros por rangos de fechas (si existen) +- DTOs de filtro utilizados +- Construcción de queries en backend +- Cómo se construyen queries con condiciones combinadas y operadores distintos de igualdad +- Cómo se envían los filtros desde Angular + +**Relaciones entre entidades** + +- Cómo se modelan relaciones en JPA +- Ejemplos existentes en el proyecto +- Cómo se representan en DTOs +- Cómo se cargan y exponen los datos relacionados + +**Validaciones en backend** + +- Dónde se implementan (Service) +- Cómo se gestionan errores +- Cómo se propagan al frontend +- Cómo se implementan validaciones sobre rangos de fechas +- Cómo se validan restricciones que dependen de registros existentes (solapamientos, límites por cliente, etc.) + +**Combos (selects) en frontend** +- Cómo se cargan datos (clientes, juegos) +- Uso de servicios Angular +- Flujo de carga en componentes + +⚠️ En esta fase: + +- **NO** se escribe código +- **NO** se diseña la solución +- **NO** se inventan estructuras nuevas + +Solo se analiza el **sistema actual**. + +--- + +**📜 Prompt** + +Lo que haremos será escribir en el chat de ``Visual Studio Code`` el comando y las instrucciones que queramos darle. ``Recuerda haber elegido Claude Sonnet 4.6 y estar trabajando en modo Agent``. + +En este caso, hemos añadido las carpetas del proyecto **``frontend`` y ``backend`` al contexto**, por lo que el análisis se realizará sobre **el sistema completo**. + +Para ello, desde el propio Chat de Copilot, pulsando el botón **“+”**, puedes seleccionar y añadir tanto **archivos individuales** como **directorios completos** del proyecto. También es posible añadirlos **arrastrándolos directamente al chat**. + +![Ventana de chat](../assets/images/specs-customer-paid_2.png) + +``` +/opsx:explore + +Analiza el proyecto actual (Angular 17 + Spring Boot) centrándote en las funcionalidades necesarias para implementar la gestión de préstamos y responde: + +1. ¿Cómo se implementa la paginación? +- Backend: uso de Page y construcción de respuestas paginadas +- Frontend: consumo de datos paginados +- Integración en tablas + +2. ¿Cómo se implementan los filtros en los listados? +- Especialmente en el catálogo de juegos +- DTOs de filtro utilizados +- Construcción de queries en backend +- Uso de condiciones combinadas (no solo igualdad) +- Ejemplos de filtros por rango de fechas (si existen) +- Ejemplos de consultas donde una fecha debe estar contenida dentro de un rango (si existen) +- Cómo se envían los filtros desde Angular + +3. ¿Cómo se gestionan relaciones entre entidades? +- Modelado en JPA +- Ejemplos en el proyecto +- Cómo se representan en DTOs +- Cómo se exponen los datos relacionados + +4. ¿Cómo se implementan validaciones en backend? +- Dónde se ubican (Service) +- Cómo se gestionan errores +- Cómo se propagan al frontend +- Si existen validaciones que dependan de múltiples registros o condiciones +- Si existen validaciones relacionadas con fechas o rangos +- Cómo se validan restricciones basadas en datos existentes + +5. ¿Cómo se cargan datos en combos (selects) en frontend? +- Servicios Angular utilizados +- Cómo se obtienen los datos +- Flujo de carga en componentes + +NO propongas soluciones. +NO diseñes la funcionalidad de préstamos. +NO repitas el análisis básico del sistema. +NO incluyas código completo. Resume la lógica cuando sea necesario. + +Céntrate únicamente en los aspectos necesarios para implementar la funcionalidad de gestión de préstamos. +``` + +Este comando realizará un análisis exhaustivo de tu sistema que servirá como base para definir la nueva funcionalidad en la siguiente fase. + +!!! tip "Sobre los permisos" + Es posible que durante el análisis te pida permiso para hacer ciertas tareas. Le puedes ir dando permiso una a una o darle permiso en todo el workspace, eso lo dejamos a tu elección. + +En cualquier momento puedes ver el consumo de la ventana de contexto para saber si todo el conocimiento del sistema está en memoria o no. En el icono de la gráfica circular que está situada en la parte inferior derecha del chat. + +![Ventana contexto](../assets/images/specs-customer-paid_3.png) + +### Propose + +Una vez analizado el sistema en la fase Explore, el siguiente paso es definir de forma clara y estructurada **la nueva funcionalidad a implementar**. + +En esta fase establecemos **qué vamos a construir**, apoyándonos en el conocimiento ya consolidado del sistema y en el resultado del Explore. + +Esta fase actúa como puente entre el análisis y la implementación, permitiendo diseñar la solución antes de escribir código y reduciendo el riesgo de errores durante el desarrollo. + +Durante esta fase debes especificar: + +**Descripción funcional** + +- Qué hace la funcionalidad +- Qué problema resuelve + +**Reglas de negocio** + +- Validaciones sobre fechas: + - La fecha de fin no podrá ser anterior a la fecha de inicio +- Restricciones de duración del préstamo: + - El período de préstamo máximo solo podrá ser de 14 días +- Validaciones de solapamiento de préstamos: + - El mismo juego no puede estar prestado a más de un cliente para ninguno de los días incluidos en el rango del préstamo +- Límites de préstamos simultáneos por cliente: + - Un mismo cliente no puede tener más de 2 préstamos activos para ninguno de los días incluidos en el rango del préstamo + +**Diseño backend** + +- Endpoints necesarios +- Estructura del dominio (Entity, DTO, Service, Repository) +- Tipo de operaciones (listado, creación, edición, borrado) +- Estrategia para filtros por fecha dentro de rangos + +**Diseño frontend** + +- Componentes necesarios +- Flujo de usuario (listado, abrir modal, guardar, borrar) +- Servicios Angular +- Gestión de combos (clientes y juegos) +- Integración de filtros y paginación +- Integración de Datepicker para filtro por fecha +- Estructura de pantalla: + - **Listado**, seguirá la estructura general de las pantallas ya existentes, reutilizando: + - Patrón de filtros de catálogo. Para este caso, se permitirá filtrar por: + - Título del juego (combo) + - Cliente (combo) + - Fecha (Datepicker): la fecha seleccionada debe estar contenida entre la fecha de inicio y la fecha de fin del préstamo + - Patrón de paginación del listado de autores + - El orden de las columnas del listado será: + - Identificador + - Nombre del juego + - Nombre del cliente + - Fecha de préstamo + - Fecha de devolución + - Las fechas se mostrarán siempre en formato DD/MM/YYYY + - **Alta/edición**: + - El identificador aparecerá vacío en creación y en modo solo lectura + - Debajo se mostrará el campo de nombre de cliente (combo seleccionable) + - Debajo se mostrará el campo de nombre de juego (combo seleccionable) + - Debajo se mostrará la sección de fechas de préstamo: la fecha de inicio y la fecha de fin estarán en la misma fila + - Todos los campos, salvo el identificador, serán obligatorios + +**Decisiones técnicas** + +- Qué patrones existentes se reutilizan +- Qué partes deben extenderse +- Cómo se gestionarán los filtros de fecha y condiciones combinadas +- Cómo se implementarán validaciones basadas en múltiples registros (solapamientos y límites) + +**Plan de implementación** + +- Tareas ordenadas +- Separación backend / frontend +- Prioridad de desarrollo (listado → filtros → validaciones) + +Aquí dejamos claro: + +- Qué funcionalidad se va a añadir +- Qué reglas de negocio existen +- Qué piezas del sistema se ven afectadas +- Qué tareas habrá que ejecutar + +⚠️ En esta fase: + +- **NO** se implementa código +- **NO** se redefine el sistema + +--- + +**📜 Prompt** + +Recuerda que seguimos trabajando en **modo Agent**, con las carpetas del proyecto **``frontend`` y ``backend`` añadidas al contexto**. + +Para nuestro ejemplo, lo que haremos será escribir en el chat de ``Visual Studio Code`` el siguiente prompt: + +``` +/opsx:propose loan + +Define la funcionalidad de gestión de préstamos basándote en el sistema actual (Angular 17 + Spring Boot), en los patrones identificados en la fase Explore y en los requisitos funcionales indicados. + +Requisitos funcionales: +- Se necesita una funcionalidad de gestión de préstamos + +- Un préstamo relaciona un cliente y un juego + +- El listado será paginado + +- Existirá una zona de filtros en la parte superior del listado + +- Se podrá filtrar por: + - Juego (combo seleccionable) + - Cliente (combo seleccionable) + - Fecha (Datepicker) + +- La fecha seleccionada deberá estar contenida entre la fecha de inicio y la fecha de fin del préstamo para que el registro aparezca en el listado + +- El listado deberá mostrar: + - Identificador + - Nombre del juego + - Nombre del cliente + - Fecha de préstamo + - Fecha de devolución + +- Las fechas se mostrarán en formato DD/MM/YYYY + +- Existirá una pantalla modal de alta / edición + +- En alta / edición: + - El identificador aparecerá vacío en creación y en modo solo lectura + - Se seleccionará cliente mediante combo + - Se seleccionará juego mediante combo + - Se introducirán fecha de inicio y fecha de fin en la misma fila + - Todos los campos, salvo el identificador, serán obligatorios + +Reglas de negocio: +- La fecha de fin no podrá ser anterior a la fecha de inicio +- El período máximo del préstamo será de 14 días +- El mismo juego no podrá estar prestado a más de un cliente para ninguno de los días incluidos en el rango del préstamo +- Un mismo cliente no podrá tener más de 2 préstamos activos para ninguno de los días incluidos en el rango del préstamo +- Las mismas validaciones aplican tanto en creación como en edición +- El backend deberá validar siempre, aunque el frontend también realice validaciones + +Define: + +1. Descripción de la funcionalidad + +2. Reglas de negocio + +3. Diseño backend: +- Endpoints necesarios +- Estructura del dominio (Entity, DTO, Service, Repository) +- Tipo de operaciones (listado, creación, edición, borrado) +- Estrategia para filtros por fecha dentro de rangos + +4. Diseño frontend: +- Componentes necesarios +- Flujo de interacción (listado, abrir modal, guardar, borrar) +- Servicios Angular +- Gestión de combos (clientes y juegos) +- Integración de filtros y paginación +- Integración de Datepicker para filtro por fecha +- Estructura funcional del listado y del formulario de alta/edición + +5. Decisiones técnicas: +- Qué patrones del sistema actual se reutilizan +- Qué partes deben extenderse +- Cómo se gestionarán los filtros de fecha y condiciones combinadas +- Cómo se implementarán validaciones basadas en múltiples registros (solapamientos y límites) + +NO implementes código. +NO analices de nuevo el proyecto. +Basa la propuesta en los patrones detectados en la fase Explore. + +``` + +Igual que en la gestión de clientes, este comando genera dentro del directorio ``changes`` la propuesta correspondiente, que incluye los siguientes ficheros: `proposal.md`, `design.md`, `spec.md`, `tasks.md` + +Estos artefactos están adaptados a la funcionalidad de **gestión de préstamos**, incorporando las reglas de negocio, filtros y validaciones específicas de este caso de uso. + +Constituyen la base para la siguiente fase: **Apply**, donde se ejecutará la implementación siguiendo las tareas definidas. + +!!! tip "Responsabilidades como developer IA" + En este punto la IA te ha hecho una propuesta que puede ser correcta o no, recordemos que se trata de un modelo matemático-probabilístico. Si hay algo de lo propuesto que no te encaja o es erróneo deberías comentarlo mediante el chat o corregirlo de forma manual en el fichero que corresponda. Por ejemplo si quieres añadir una tarea porqué se te ha olvidado incluirla en el prompt original, deberías decirle al modelo que te incluya la nueva tarea. + +Una vez estemos de acuerdo con la propuesta que nos ha hecho la IA, podemos pasar al siguiente punto. + +### Apply + +Una vez validada la propuesta, ejecutamos la implementación: + +El objetivo de esta fase es transformar los artefactos generados +(`proposal.md`, `design.md`, `spec.md`, `tasks.md`) en **código funcional**, asegurando que: + +- Se respetan los requisitos funcionales definidos en `spec.md` +- Se siguen las decisiones técnicas establecidas en `design.md` +- Se ejecutan las tareas en el orden definido en `tasks.md` + +--- + +**📜 Prompt** + +Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguiente prompt: + +``` +/opsx:apply +``` + +El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado tanto en la carpeta ``backend`` como en la carpeta ``frontend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. + +## Pruebas + +Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. + +Arranca el **backend** y el **frontend** y verifica: + +- La aplicación levanta correctamente +- Las nuevas funcionalidades añadidas están accesibles +- Los flujos principales definidos en `spec.md` funcionan como se espera + +!!! warning "Ojo no te fies" + Ojo no te fies de todo lo que construya la IA. Tu estás al mando, tu debes decidir si el sistema está correctamente implementado o no. Es tu responsabilidad. + +Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, es el momento de escribirlo por el chat indicándole exactamente que es lo que falta. Cuanto más preciso y conciso seas, mejor implementará la IA. + +### Archive + +Y llegamos a la última etapa que nos define Open Spec, donde se archiva el cambio y se da por finalizada la funcionalidad. + +El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. + +--- + +**📜 Prompt** + +De nuevo nos vamos al chat de ``Visual Studio Code`` el siguiente prompt: + +``` +/opsx:archive +``` + +Durante el proceso de Archive, el sistema solicitará confirmación para sincronizar los requisitos antes de archivar el cambio. + +Recuerda que al sincronizar, los requisitos definidos en `spec.md` pasan de ser un cambio temporal a formar parte permanente del sistema. + +Si no se sincroniza, el código queda implementado, pero los requisitos no se registran en los specs principales afectando a la trazabilidad y futuras evoluciones del sistema. \ No newline at end of file From 219b60345aca98510ed4585d047e001708d491ce Mon Sep 17 00:00:00 2001 From: lauramgll <156073035+lauramgll@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:47:43 +0200 Subject: [PATCH 09/13] Intro --- docs/specs/customers_free.md | 14 ++-- docs/specs/customers_paid.md | 12 +-- docs/specs/intro.md | 141 ++++++++++++++++++++++++++++++++++- docs/specs/loans_paid.md | 6 +- 4 files changed, 153 insertions(+), 20 deletions(-) diff --git a/docs/specs/customers_free.md b/docs/specs/customers_free.md index 24e41a3..4c410c7 100644 --- a/docs/specs/customers_free.md +++ b/docs/specs/customers_free.md @@ -27,7 +27,7 @@ La estructura debería ser similar a esta: ![workspace](../assets/images/specs-install_1.png) -Desde consola (o desde el terminal de tu IDE), nos situamos en el **directorio raíz** y lanzamos el inicializador de Open Spec. +Desde consola (o desde el terminal de tu IDE), nos situamos en el **directorio raíz** y lanzamos el inicializador de OpenSpec. ``` openspec init @@ -37,7 +37,7 @@ Seleccionamos **``GitHub Copilot``**, pulsamos **``Enter``** para añadirlo y de ![workspace](../assets/images/specs-install_2.png) -Esto instalará las plantillas necesarias para poder trabajar con **Open Spec + GitHub Copilot**. +Esto instalará las plantillas necesarias para poder trabajar con **OpenSpec + GitHub Copilot**. ## Consejos antes de empezar @@ -80,7 +80,7 @@ Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ## Generación de backend -Seguiremos el ciclo completo de Open Spec: +Seguiremos el ciclo completo de OpenSpec: ``` 1. Explore @@ -379,7 +379,7 @@ Responde a: ¿Cómo se implementa paso a paso? **Relación entre los artefactos** -Cada uno de los ficheros generados cumple un rol específico dentro del flujo de Open Spec: +Cada uno de los ficheros generados cumple un rol específico dentro del flujo de OpenSpec: - **spec.md** → define el comportamiento esperado (*qué debe hacer el sistema*) - **design.md** → define la solución técnica (*cómo se va a construir*) @@ -428,7 +428,7 @@ El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posi ### Pruebas del backend -Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. +Un paso que no pertenece a OpenSpec pero que es altamente recomendable es probar los cambios realizados. Arranca el backend y verifica: - Que el servidor levanta @@ -445,7 +445,7 @@ Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, e ### Archive -Y llegamos a la última etapa que nos define Open Spec, donde se archiva el cambio y se da por finalizada la funcionalidad. +Y llegamos a la última etapa que nos define OpenSpec, donde se archiva el cambio y se da por finalizada la funcionalidad. El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. @@ -516,7 +516,7 @@ Actualiza el fichero de backend-explore con los nuevos datos implementados ## Generación de frontend -Bueno, pues ahora que ya tenemos el backend implementado, realizaremos de nuevo un ciclo completo de Open Spec pero está vez para frontend: +Bueno, pues ahora que ya tenemos el backend implementado, realizaremos de nuevo un ciclo completo de OpenSpec pero está vez para frontend: ``` 1. Explore diff --git a/docs/specs/customers_paid.md b/docs/specs/customers_paid.md index 19317ed..65a215a 100644 --- a/docs/specs/customers_paid.md +++ b/docs/specs/customers_paid.md @@ -27,7 +27,7 @@ La estructura debería ser similar a esta: ![workspace](../assets/images/specs-install_1.png) -Desde consola (o desde el terminal de tu IDE), nos situamos en el **directorio raíz** y lanzamos el inicializador de Open Spec. +Desde consola (o desde el terminal de tu IDE), nos situamos en el **directorio raíz** y lanzamos el inicializador de OpenSpec. ``` openspec init @@ -37,7 +37,7 @@ Seleccionamos **``GitHub Copilot``**, pulsamos **``Enter``** para añadirlo y de ![workspace](../assets/images/specs-install_2.png) -Esto instalará las plantillas necesarias para poder trabajar con **Open Spec + GitHub Copilot**. +Esto instalará las plantillas necesarias para poder trabajar con **OpenSpec + GitHub Copilot**. ## Consejos antes de empezar @@ -77,7 +77,7 @@ Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ## Desarrollo de la funcionalidad -Seguiremos el ciclo completo de Open Spec: +Seguiremos el ciclo completo de OpenSpec: ``` 1. Explore @@ -358,7 +358,7 @@ Responde a: ¿Cómo se implementa paso a paso? **Relación entre los artefactos** -Cada uno de los ficheros generados cumple un rol específico dentro del flujo de Open Spec: +Cada uno de los ficheros generados cumple un rol específico dentro del flujo de OpenSpec: - **spec.md** → define el comportamiento esperado (*qué debe hacer el sistema*) - **design.md** → define la solución técnica (*cómo se va a construir*) @@ -403,7 +403,7 @@ El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posi ## Pruebas -Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. +Un paso que no pertenece a OpenSpec pero que es altamente recomendable es probar los cambios realizados. Arranca el **backend** y el **frontend** y verifica: @@ -419,7 +419,7 @@ Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, e ### Archive -Y llegamos a la última etapa que nos define Open Spec, donde se archiva el cambio y se da por finalizada la funcionalidad. +Y llegamos a la última etapa que nos define OpenSpec, donde se archiva el cambio y se da por finalizada la funcionalidad. El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. diff --git a/docs/specs/intro.md b/docs/specs/intro.md index 930eaaf..7de0030 100644 --- a/docs/specs/intro.md +++ b/docs/specs/intro.md @@ -1,13 +1,146 @@ # Introducción a Spec-Driven Development !!! warning Atención - Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente + Esta sección se encuentra en desarrollo 🚧. + **NO se recomienda realizarla** a menos que te lo hayan indicado expresamente. +## Contexto de la implementación +En esta sección se describe la implementación de nuevas funcionalidades en la aplicación siguiendo un enfoque de **Spec-Driven Development (SDD)**. -Primero contar que vamos a hacer una implementación alternativa utilizado Spec Drive Development (la intro del word) +El objetivo no es únicamente desarrollar nuevas funcionalidades, sino hacerlo siguiendo un proceso estructurado que permita separar claramente las distintas fases del desarrollo y garantizar **trazabilidad entre análisis, definición, implementación y validación**. -Luego explicar en que consiste SDD (muy por encima un párrafo o dos sacado de copilot) +Para llevar a cabo este enfoque de manera práctica, se utiliza la metodología **OpenSpec**, que proporciona un flujo de trabajo claro y repetible para definir, implementar y consolidar cambios en el sistema. -Luego la metodología OpenSpec (que ya está en el word) +Las funcionalidades abordadas se organizan en dos bloques principales: +- **Gestión de clientes** +- **Gestión de préstamos** + +Ambas se implementan siguiendo las fases definidas por **OpenSpec**, reutilizando los patrones existentes del sistema y manteniendo coherencia técnica y funcional con el resto de la aplicación. + +--- + +## ¿Qué es Spec-Driven Development? + +**Spec-Driven Development (SDD)** es un enfoque de desarrollo en el que el comportamiento del sistema se define de forma explícita **antes de escribir código**. + +En lugar de comenzar directamente con la implementación, SDD propone describir primero: + +- Qué debe hacer el sistema +- Qué reglas y restricciones deben cumplirse +- Qué comportamiento se espera en cada escenario + +Las especificaciones (*specs*) se convierten en el eje central del desarrollo y sirven como referencia común durante todo el proceso. + +Este enfoque permite: + +- Reducir ambigüedades sobre el comportamiento esperado +- Detectar errores de diseño de forma temprana +- Mantener coherencia en sistemas que evolucionan con el tiempo +- Facilitar la comunicación entre personas y herramientas implicadas en el desarrollo + +--- + +## OpenSpec como metodología de trabajo + +**OpenSpec** es una metodología que materializa el enfoque de **Spec-Driven Development**, proporcionando un flujo de trabajo estructurado para implementar cambios de forma controlada y trazable. + +OpenSpec organiza el desarrollo en una serie de fases bien definidas que permiten: + +- Analizar el contexto y el alcance del cambio +- Definir el comportamiento funcional esperado +- Implementar la solución de forma alineada con lo definido +- Cerrar y consolidar el cambio de manera ordenada + +A lo largo de esta guía se utilizará OpenSpec como marco de trabajo para aplicar SDD en la implementación de las funcionalidades de gestión de clientes y gestión de préstamos. + +!!! tip "SDD y agentes de IA" + Al trabajar con agentes de IA, el código puede generarse rápidamente, pero sin una guía clara existe el riesgo de que la IA tome decisiones no deseadas para que “el código funcione”. + + OpenSpec traslada el foco a las especificaciones, que definen explícitamente el comportamiento esperado del sistema y sirven como contrato para la IA, reduciendo ambigüedades y asegurando trazabilidad entre lo definido y lo implementado. + +## Fases de OpenSpec + +El flujo de trabajo de OpenSpec se estructura en cuatro fases principales: + +``` +1. Explore +2. Propose +3. Apply +4. Archive +``` + +Cada una de estas fases cumple un propósito específico y se apoya en la anterior, formando un ciclo completo de definición, implementación y cierre del cambio. + +--- + +### Explore + +Fase inicial orientada a **comprender el contexto** en el que se va a trabajar. + +**Objetivo** + +- Analizar la información disponible +- Entender la necesidad, iniciativa o cambio a abordar +- Identificar posibles limitaciones, dependencias y patrones existentes + +Esta fase no implica necesariamente la existencia de un sistema previo. + +Puede consistir en: + +- Analizar un sistema existente +- Revisar documentación o requisitos +- Definir el contexto cuando se parte desde cero + +**Resultado** + +Una comprensión clara del punto de partida y del alcance del cambio a realizar. + +--- + +### Propose + +Fase orientada a la **definición de la solución** a implementar. + +**Objetivo** + +- Definir qué se va a construir +- Delimitar el alcance del cambio (qué se incluye y qué queda fuera) +- Establecer el comportamiento funcional esperado + +**Resultado** + +Una propuesta clara, estructurada y alineada con el objetivo del cambio, que servirá como base para su implementación. + +--- + +### Apply + +Fase en la que se lleva a cabo la **implementación** de la solución definida en la fase Propose. + +**Objetivo** + +- Desarrollar la solución definida +- Asegurar la coherencia entre lo definido y lo implementado +- Integrar y validar funcionalmente el resultado + +**Resultado** + +Una solución implementada, coherente con la propuesta definida y preparada para su validación final. + +--- + +### Archive + +Fase final de **cierre y consolidación** del cambio. + +**Objetivo** + +- Confirmar que el cambio está completo +- Consolidar la documentación generada durante el proceso +- Garantizar la trazabilidad para futuras evoluciones + +**Resultado** + +Un cambio finalizado, validado y correctamente documentado. \ No newline at end of file diff --git a/docs/specs/loans_paid.md b/docs/specs/loans_paid.md index 750cc51..e187aa9 100644 --- a/docs/specs/loans_paid.md +++ b/docs/specs/loans_paid.md @@ -50,7 +50,7 @@ Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ## Desarrollo de la funcionalidad -Seguiremos el ciclo completo de Open Spec: +Seguiremos el ciclo completo de OpenSpec: ``` 1. Explore @@ -395,7 +395,7 @@ El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posi ## Pruebas -Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. +Un paso que no pertenece a OpenSpec pero que es altamente recomendable es probar los cambios realizados. Arranca el **backend** y el **frontend** y verifica: @@ -410,7 +410,7 @@ Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, e ### Archive -Y llegamos a la última etapa que nos define Open Spec, donde se archiva el cambio y se da por finalizada la funcionalidad. +Y llegamos a la última etapa que nos define OpenSpec, donde se archiva el cambio y se da por finalizada la funcionalidad. El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. From 56742c16671263581392c6f0e4f4faa3783b3ae2 Mon Sep 17 00:00:00 2001 From: Pablo Jimenez Date: Wed, 22 Apr 2026 10:49:24 +0200 Subject: [PATCH 10/13] Ejercicio 2 free --- docs/assets/images/specs-customer-free_4.png | Bin 0 -> 17384 bytes docs/specs/customers_free.md | 4 +- docs/specs/customers_paid.md | 2 +- docs/specs/loans_free.md | 656 ++++++++++++++++++- docs/specs/loans_paid.md | 7 +- 5 files changed, 661 insertions(+), 8 deletions(-) create mode 100644 docs/assets/images/specs-customer-free_4.png diff --git a/docs/assets/images/specs-customer-free_4.png b/docs/assets/images/specs-customer-free_4.png new file mode 100644 index 0000000000000000000000000000000000000000..ce918a4f503f19b4c6782a9fd808382f3f1a8532 GIT binary patch literal 17384 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 P!|FUTd{}+o>E-_hwZTo7 literal 0 HcmV?d00001 diff --git a/docs/specs/customers_free.md b/docs/specs/customers_free.md index 24e41a3..669df31 100644 --- a/docs/specs/customers_free.md +++ b/docs/specs/customers_free.md @@ -525,6 +525,8 @@ Bueno, pues ahora que ya tenemos el backend implementado, realizaremos de nuevo 4. Archive ``` +**Es muy importante** que cada nuevo cambio que hagamos, lo empecemos en un chat nuevo, para limpiar el contexto anterior y no arrastrar posibles errores o incoherencias. + ### Explore De nuevo el objetivo de esta fase es analizar el sistema existente, sin modificar nada, pero esta vez nos centraremos en el frontend. @@ -533,7 +535,7 @@ De nuevo el objetivo de esta fase es analizar el sistema existente, sin modifica **📜 Prompt** -Vamos al chat de ``Visual Studio Code`` y escribimos el comando: +Vamos **a un nuevo** chat de ``Visual Studio Code`` y escribimos el comando: ``` opsx:explore diff --git a/docs/specs/customers_paid.md b/docs/specs/customers_paid.md index 19317ed..794d7a6 100644 --- a/docs/specs/customers_paid.md +++ b/docs/specs/customers_paid.md @@ -401,7 +401,7 @@ Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguien El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado tanto en la carpeta ``backend`` como en la carpeta ``frontend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. -## Pruebas +### Pruebas Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. diff --git a/docs/specs/loans_free.md b/docs/specs/loans_free.md index e61c3c4..585b36e 100644 --- a/docs/specs/loans_free.md +++ b/docs/specs/loans_free.md @@ -1,11 +1,659 @@ # Gestión de préstamos (modelo gratuito) + !!! warning Atención - Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente + Esta sección se encuentra en desarrollo 🚧. + **NO se recomienda realizarla** a menos que te lo hayan indicado expresamente. + +## Punto de partida + +Si has llegado hasta aquí, entiendo que ya has completado la funcionalidad de **gestión de clientes** utilizando el modelo gratuito. + +A partir de ahora vamos a dar por hecho que partimos de ese estado del sistema, donde: + +- Existe un CRUD funcional de clientes +- La funcionalidad está implementada, validada y archivada +- Los patrones de backend y frontend introducidos ya forman parte del sistema + +Una vez llegados a este punto, asumimos que el proyecto **ya está descargado y configurado**, y que hemos trabajado previamente sobre la funcionalidad de **gestión de clientes**. + +Por tanto, **continuaremos utilizando los mismos proyectos y directorios**, sin realizar ninguna instalación ni configuración adicional. + +En este tutorial seguiremos trabajando sobre: + +- ``server-springboot`` como **``backend``** +- ``client-angular17`` como **``frontend``** + + +## Consejos antes de empezar + +Continuaremos trabajando con un **modelo gratuito**, utilizando **``Claude Haiku``** y el mismo workspace que en la funcionalidad de **gestión de clientes**. + +Antes de comenzar, ten en cuenta lo siguiente: + +- Para cada **nueva funcionalidad**, es recomendable iniciar una **nueva conversación de chat** dentro del mismo proyecto + +Esto ayuda a mantener el contexto limpio y a que el modelo se centre exclusivamente en la funcionalidad que vamos a abordar. + +Recuerda que en cualquier momento puedes ver el consumo mensual de tu cuenta pulsando el icono de la rana 🐸 en la esquina inferior derecha. El contador **se reinicia cada mes**. + +## Estrategia de trabajo + +Al igual que el ejercicio anterior, vamos a dividir el ejercicio en **dos grandes bloques**: + +1. Primero trabajaremos únicamente con el **``backend``** +2. Después abordaremos el **``frontend``** + +De esta forma limitamos el contexto a un solo proyecto y facilitamos el trabajo al modelo. + + +Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ti te genera algo diferente a lo que ves aquí, probablemente seguirá siendo válido. No te frustres y ajusta los prompts si es necesario. + +## Desarrollo de la funcionalidad + +Seguiremos el ciclo completo de Open Spec: + +``` +1. Explore +2. Propose +3. Apply +4. Archive +``` + +## Generación de backend + +Aunque no es obligatorio, es altamente recomendable volver a ejecutar la fase de **``Explore``**. El sistema ha podido cambiar desde tu último cambio, alguien ha podido hacer modificaciones, etc. En tu caso no sería necesario ya que estás trabajando tu solo y no has cambiado nada, pero es buena práctica hacerlo siempre. + + +### Explore + +El objetivo de esta fase es **analizar el sistema existente**, sin modificar nada. + +A diferencia de la gestión de clientes, este caso de uso introduce una mayor complejidad, principalmente por: + +- Relaciones entre entidades (cliente, juego, préstamo) +- Uso de **paginación** en los listados +- Aplicación de **filtros combinados** +- Necesidad de **validaciones de negocio más complejas** + +En esta fase se analizará qué partes del sistema actual ya resuelven este tipo de problemas y pueden reutilizarse, y qué aspectos no están implementados y deberán abordarse en fases posteriores. + +Aspectos a revisar: + +**Paginación** + +- Cómo se implementa en backend (uso de Page) + +**Filtros** + +- Cómo se implementa en el catálogo de juegos +- Cómo se implementan filtros por rangos de fechas (si existen) +- DTOs de filtro utilizados +- Construcción de queries en backend +- Cómo se construyen queries con condiciones combinadas y operadores distintos de igualdad + +**Relaciones entre entidades** + +- Cómo se modelan relaciones en JPA +- Ejemplos existentes en el proyecto +- Cómo se representan en DTOs +- Cómo se cargan y exponen los datos relacionados + +**Validaciones en backend** + +- Dónde se implementan (Service) +- Cómo se gestionan errores +- Cómo se propagan al frontend +- Cómo se implementan validaciones sobre rangos de fechas +- Cómo se validan restricciones que dependen de registros existentes (solapamientos, límites por cliente, etc.) + +⚠️ En esta fase: + +- **NO** se escribe código +- **NO** se diseña la solución +- **NO** se inventan estructuras nuevas + +Solo se analiza el **sistema actual**. + +--- + +**📜 Prompt** + +Lo que haremos será escribir en el chat de ``Visual Studio Code`` el comando y las instrucciones que queramos darle. ``Recuerda haber elegido Claude Haiku y estar trabajando en modo Agent``. + +``` + +/opsx:explore + +Analiza el proyecto actual que está en el directorio "backend", es una aplicación Spring Boot. Una vez analizado, responde: + +1. ¿Cómo están implementados los CRUD existentes? +- Controller +- Service +- Repository +- Paginación y respuestas paginadas + +2. ¿Qué estructura siguen los dominios? + +3. ¿Cómo se implementan las operaciones? +- Listado +- Creación/edición +- Borrado +- DTOs de filtro utilizados y construcción de queries en backend +- Uso de condiciones combinadas (no solo igualdad) +- Ejemplos de filtros por rango de fechas (si existen) +- Ejemplos de consultas donde una fecha debe estar contenida dentro de un rango (si existen) + +4. ¿Cómo se gestionan relaciones entre entidades? +- Modelado en JPA +- Ejemplos en el proyecto +- Cómo se representan en DTOs +- Cómo se exponen los datos relacionados + +5. ¿Cómo se implementan validaciones en backend? +- Dónde se ubican (Service) +- Cómo se gestionan errores +- Cómo se propagan al frontend +- Si existen validaciones que dependan de múltiples registros o condiciones +- Si existen validaciones relacionadas con fechas o rangos +- Cómo se validan restricciones basadas en datos existentes + +6. ¿Qué formato tienen los endpoints y que relación tiene con los métodos HTTP? + +7. ¿Qué patrones o estructuras comunes se repiten en los CRUD existentes? +- Clases reutilizables +- Lógica repetida +- Estructuras comunes entre dominios + +8. ¿Existen test unitarios y de integración? ¿Cómo están implementados? ¿Utiliza algo especial al arrancar o al mockear? + + +Analiza únicamente la parte de backend (Spring Boot) +NO propongas soluciones. +NO diseñes nuevas funcionalidades. +Solo analiza el sistema actual. +Ya tienes un contexto previo en el fichero backend-explore.md en el directorio de las specs, utilizalo y lo actualizas con lo que analices y no esté. + +``` + +Si te fijas, le hemos indicado que aproveche el contexto previo generado en el ejercicio anterior y le hemos pedido que lo actualice con los cambios que considere. + + +Este comando realizará un análisis exhaustivo de tu sistema que servirá como base para definir la nueva funcionalidad en la siguiente fase. + +!!! tip "Sobre los permisos" + Es posible que durante el análisis te pida permiso para hacer ciertas tareas. Le puedes ir dando permiso una a una o darle permiso en todo el workspace, eso lo dejamos a tu elección. + +### Propose + +Una vez analizado el sistema en la fase Explore, el siguiente paso es definir de forma clara y estructurada **la nueva funcionalidad a implementar**. + +En esta fase establecemos **qué vamos a construir**, apoyándonos en el conocimiento ya consolidado del sistema y en el resultado del Explore. + +Esta fase actúa como puente entre el análisis y la implementación, permitiendo diseñar la solución antes de escribir código y reduciendo el riesgo de errores durante el desarrollo. + +Durante esta fase debes especificar: + +**Descripción funcional** + +- Qué hace la funcionalidad +- Qué problema resuelve + +**Reglas de negocio** + +- Validaciones sobre fechas: + - La fecha de fin no podrá ser anterior a la fecha de inicio +- Restricciones de duración del préstamo: + - El período de préstamo máximo solo podrá ser de 14 días +- Validaciones de solapamiento de préstamos: + - El mismo juego no puede estar prestado a más de un cliente para ninguno de los días incluidos en el rango del préstamo +- Límites de préstamos simultáneos por cliente: + - Un mismo cliente no puede tener más de 2 préstamos activos para ninguno de los días incluidos en el rango del préstamo + +**Diseño backend** + +- Endpoints necesarios +- Estructura del dominio (Entity, DTO, Service, Repository) +- Tipo de operaciones (listado, creación, edición, borrado) +- Estrategia para filtros por fecha dentro de rangos + +**Decisiones técnicas** + +- Qué patrones existentes se reutilizan +- Qué partes deben extenderse +- Cómo se gestionarán los filtros de fecha y condiciones combinadas +- Cómo se implementarán validaciones basadas en múltiples registros (solapamientos y límites) + +**Plan de implementación** + +- Tareas ordenadas +- Separación backend / frontend +- Prioridad de desarrollo (listado → filtros → validaciones) + +Aquí dejamos claro: + +- Qué funcionalidad se va a añadir +- Qué reglas de negocio existen +- Qué piezas del sistema se ven afectadas +- Qué tareas habrá que ejecutar + +⚠️ En esta fase: + +- **NO** se implementa código +- **NO** se redefine el sistema + +--- + +**📜 Prompt** + +Para nuestro ejemplo, lo que haremos será escribir en el chat de ``Visual Studio Code`` el siguiente prompt: + +``` +/opsx:propose manage-loans-backend + +Define la funcionalidad de gestión de préstamos de juegos basándote en el sistema actual y en los patrones identificados en la fase Explore, tienes el resultado en el fichero "backend-explore.md". + +Nos han pedido esta nueva funcionalidad. + +Se quiere hacer uso de su catálogo de juegos y de sus clientes, y quiere saber que juegos ha prestado a cada cliente. Para ello nos ha pedido una página bastante compleja donde se podrá consultar diferente información y se permitirá realizar el préstamo de los juegos. + +Nos ha pasado el siguiente boceto y requisitos. + +La pantalla tendrá dos zonas: + +- Una zona de filtrado donde se permitirá filtrar por: + - Título del juego, que deberá ser un combo seleccionable con los juegos del catálogo de la Ludoteca. + - Cliente, que deberá ser un combo seleccionable con los clientes dados de alta en la aplicación. + - Fecha, que deberá ser de tipo Datepicker y que permitirá elegir una fecha de búsqueda. Al elegir un día nos deberá mostrar que juegos están prestados para dicho día. OJO que los préstamos son con fecha de inicio y de fin, si elijo un día intermedio debería aparecer el elemento en la tabla. +- Una zona de listado paginado que deberá mostrar + - El identificador del préstamo + - El nombre del juego prestado + - El nombre del cliente que lo solicitó + - La fecha de inicio del préstamo + - La fecha de fin del préstamo + - Un botón que permite eliminar el préstamo + + +Al pulsar el botón de Nuevo préstamo se abrirá una pantalla donde se podrá ingresar la siguiente información, toda ella obligatoria: +- Identificador, inicialmente vacío y en modo lectura +- Nombre del cliente, mediante un combo seleccionable +- Nombre del juego, mediante un combo seleccionable +- Fechas del préstamo, donde se podrá introducir dos fechas, de inicio y fin del préstamo. + +Las validaciones son sencillas aunque laboriosas: +- La fecha de fin NO podrá ser anterior a la fecha de inicio +- El periodo de préstamo máximo solo podrá ser de 14 días. Si el usuario quiere un préstamo para más de 14 días la aplicación no debe permitirlo mostrando una alerta al intentar guardar. +- El mismo juego no puede estar prestado a dos clientes distintos en un mismo día. OJO que los préstamos tienen fecha de inicio y fecha fin, el juego no puede estar prestado a más de un cliente para ninguno de los días que contemplan las fechas actuales del rango. +- Un mismo cliente no puede tener prestados más de 2 juegos en un mismo día. OJO que los préstamos tienen fecha de inicio y fecha fin, el cliente no puede tener más de dos préstamos para ninguno de los días que contemplan las fechas actuales del rango. + +Para empezar te daré unos consejos: + +- Recuerda crear la tabla de la BBDD y sus datos +- Intenta primero hacer el listado paginado sin filtros, en el orden que más te guste: frontend o backend. Recuerda que se trata de un listado paginado, así que deberás utilizar el objeto Page. +- Completa el listado conectando ambas capas. +- Ahora implementa los filtros, presta atención al filtro de fecha, es el más complejo. +- Para la paginación filtrada solo tienes que mezclar los conceptos que hemos visto en los puntos del tutorial anteriores. +- Si hiciste el backend en Springboot recuerda revisar Baeldung por si tienes dudas sobre las queries y recuerda que las Specifications son muy útiles, pero en este caso deberás implementar otro tipo de operaciones, no te sirve solo con la operación de igualdad :, que ya vimos en el tutorial. +- Implementa la pantalla de alta de préstamo, sin ninguna validación. +- Cuando ya te funcione, intenta ir añadiendo una a una las validaciones. Algunas de ellas pueden hacerse en frontend, mientras que otras deberán validarse en backend +- Os recordamos que han de poder crearse y editarse préstamos según las reglas de validación indicadas anteriormente. Aplican las mismas reglas para ambas operaciones. +- El Backend ha de validar siempre, independientemente de que el Frontend ya lo haya validado. Nunca confíes de manera exclusiva en terceras partes (Frontend o en otro Backend). + + + + +Te voy a dar otras directrices que pienso que te pueden servir: +- Se necesita un CRUD de prestamos +- Debe tener una búsqueda y una paginación, todo en el mismo endpoint +- Fíjate en como están relacionadas las entidades del modelo ya que aquí tendrás que relacionar juego y cliente +- Tienes que implementar las validaciones dentro del método de guardado y creación y, siempre que se pueda, la validación se debe delegar en una query de BBDD. + + +Necesito que definas: + +1. Descripción de la funcionalidad + +2. Reglas de negocio + +3. Diseño backend: +- Endpoints necesarios +- Estructura del dominio (Entity, DTO, Service, Repository) + +4. Decisiones técnicas: +- Qué patrones del sistema actual se reutilizan + + +NO implementes código. +NO analices de nuevo el proyecto. +Basa la propuesta en los patrones detectados en la fase Explore. +Haz la propuesta únicamente de backend. +Como última tarea añade al fichero de tasks generar un resumen del cambio realizado, con el contrato de los endpoints y la información necesaria para que luego el frontend pueda implementar sus llamadas de forma sencilla. + +Tendrás que escribir los ficheros de proposal, design, spec y tasks en la propuesta correspondiente. + +``` + +Igual que en la gestión de clientes, este comando genera dentro del directorio ``changes`` la propuesta correspondiente, que incluye los siguientes ficheros: `proposal.md`, `design.md`, `spec.md`, `tasks.md`. + +Estos artefactos están adaptados a la funcionalidad de **gestión de préstamos**, incorporando las reglas de negocio, filtros y validaciones específicas de este caso de uso. + +Constituyen la base para la siguiente fase: **Apply**, donde se ejecutará la implementación siguiendo las tareas definidas. + +!!! tip "Responsabilidades como developer IA" + En este punto la IA te ha hecho una propuesta que puede ser correcta o no, recordemos que se trata de un modelo matemático-probabilístico. Si hay algo de lo propuesto que no te encaja o es erróneo deberías comentarlo mediante el chat o corregirlo de forma manual en el fichero que corresponda. Por ejemplo si quieres añadir una tarea porqué se te ha olvidado incluirla en el prompt original, deberías decirle al modelo que te incluya la nueva tarea. + +Una vez estemos de acuerdo con la propuesta que nos ha hecho la IA, podemos pasar al siguiente punto. + +### Apply + +Una vez validada la propuesta, ejecutamos la implementación: + +El objetivo de esta fase es transformar los artefactos generados +(`proposal.md`, `design.md`, `spec.md`, `tasks.md`) en **código funcional**, asegurando que: + +- Se respetan los requisitos funcionales definidos en `spec.md` +- Se siguen las decisiones técnicas establecidas en `design.md` +- Se ejecutan las tareas en el orden definido en `tasks.md` + +--- + +**📜 Prompt** + +Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguiente prompt: + +``` +/opsx:apply +``` + +El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado dentro de la carpeta de ``backend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. + +### Pruebas del backend + +Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. +Arranca el backend y verifica: + +- Que el servidor levanta +- Que los endpoints existen y funcionan +- Que los tests pasan + +!!! warning "Ojo no te fies" + Ojo no te fies de todo lo que construya la IA. Tu estás al mando, tu debes decidir si el sistema está correctamente implementado o no. Es tu responsabilidad. + +Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, es el momento de escribirlo por el chat indicándole exactamente que es lo que falta. Cuanto más preciso y conciso seas, mejor implementará la IA. + + + + + +### Archive + +Y llegamos a la última etapa que nos define Open Spec, donde se archiva el cambio y se da por finalizada la funcionalidad. + +El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. + +--- + +**📜 Prompt** + +De nuevo nos vamos al chat de ``Visual Studio Code`` el siguiente prompt: + +``` + +/opsx:archive + +``` + +Durante el proceso de Archive, el sistema solicitará confirmación para sincronizar los requisitos antes de archivar el cambio. + +Recuerda que al sincronizar, los requisitos definidos en `spec.md` pasan de ser un cambio temporal a formar parte permanente del sistema. + +Si no se sincroniza, el código queda implementado, pero los requisitos no se registran en los specs principales afectando a la trazabilidad y futuras evoluciones del sistema. + + +**📜 Actualización del contexto** + + +Además, para forzar al ``modelo gratuito`` y dejarlo todo listo, es recomendable lanzar un último prompt que nos actualice el fichero de `backend-explore.md` + +``` + +Actualiza el fichero de backend-explore con los nuevos datos implementados + +``` + + +## Generación de frontend + +Una vez implementado el backend, nos ponemos a trabajar con el frontend. De nuevo recordar que **es muy importante** que cada nuevo cambio que hagamos, lo empecemos en un chat nuevo, para limpiar el contexto anterior y no arrastrar posibles errores o incoherencias. + +### Explore + +Al igual que en backend, aquí también lanzamos una exploración del sistema por si hubiera algún cambio con respecto a la anterior versión. + +--- + +**📜 Prompt** + +Vamos **a un nuevo** chat de ``Visual Studio Code`` y escribimos el comando: + +``` + +/opsx:explore + +Analiza el proyecto actual que está en el directorio "frontend", es una aplicación Angular. Ojo no escanees la carpeta de "node_modules" no tiene sentido. Una vez analizado, responde: + +1. ¿Cómo están implementados los CRUD existentes? +- Componentes +- Servicios +- Modelos + +2. ¿Qué estructura siguen los dominios? + +3. ¿Cómo se implementan las operaciones? +- Listado +- Creación/edición +- Borrado +- Cómo funcionan las ventanas de creación y edición (modales) + +4. ¿Como se comunican frontend con backend? +- Servicios en Angular +- Construcción de URLs + +5. ¿Cómo se implementa la paginación? +- Consumo de datos paginados +- Integración en tablas + + +6. ¿Cómo se implementan los filtros en los listados? +- Especialmente en el catálogo de juegos +- Cómo se envían los filtros desde Angular + +7. ¿Cómo se cargan datos en combos (selects) en frontend? +- Servicios Angular utilizados +- Cómo se obtienen los datos +- Flujo de carga en componentes + + +8. ¿Qué patrones o estructuras comunes se repiten en los CRUD existentes? +- Clases reutilizables +- Lógica repetida +- Estructuras comunes entre dominios + + +Analiza únicamente la parte de frontend (Angular) +NO propongas soluciones. +NO diseñes nuevas funcionalidades. +Solo analiza el sistema actual. +Ya tienes un contexto previo en el fichero frontend-explore.md en el directorio de las specs, utilizalo y lo actualizas con lo que analices y no esté. + +``` + +Si te fijas en este explore hemos añadido tanto la paginación como los filtros. Al finalizar debería actualizar el fichero explore de frontend y además ofrecernos un resumen. + +### Propose + +Una vez analizado el sistema en la fase Explore, el siguiente paso es definir de forma clara y estructurada **la nueva funcionalidad a implementar**. + +**📜 Prompt** + +De nuevo en el chat de ``Visual Studio Code`` escribimos el siguiente prompt: + +``` + +/opsx:propose manage-loans-frontend + +Define la funcionalidad de gestión de préstamos de juegos basándote en el sistema actual y en los patrones identificados en la fase Explore, tienes el resultado en el fichero "frontend-explore.md". Además tendrás que ver el cambio realizado en la spec de "manage-loans-backend", sobre todo los endpoints generados. Por si acaso también deberías tener en cuenta el fichero de "backend-explore.md". + + +Nos han pedido esta nueva funcionalidad. + +Se quiere hacer uso de su catálogo de juegos y de sus clientes, y quiere saber que juegos ha prestado a cada cliente. Para ello nos ha pedido una página bastante compleja donde se podrá consultar diferente información y se permitirá realizar el préstamo de los juegos. + +Nos ha pasado el siguiente boceto y requisitos. + +La pantalla tendrá dos zonas: + +- Una zona de filtrado donde se permitirá filtrar por: + - Título del juego, que deberá ser un combo seleccionable con los juegos del catálogo de la Ludoteca. + - Cliente, que deberá ser un combo seleccionable con los clientes dados de alta en la aplicación. + - Fecha, que deberá ser de tipo Datepicker y que permitirá elegir una fecha de búsqueda. Al elegir un día nos deberá mostrar que juegos están prestados para dicho día. OJO que los préstamos son con fecha de inicio y de fin, si elijo un día intermedio debería aparecer el elemento en la tabla. +- Una zona de listado paginado que deberá mostrar + - El identificador del préstamo + - El nombre del juego prestado + - El nombre del cliente que lo solicitó + - La fecha de inicio del préstamo + - La fecha de fin del préstamo + - Un botón que permite eliminar el préstamo + + +Al pulsar el botón de Nuevo préstamo se abrirá una pantalla donde se podrá ingresar la siguiente información, toda ella obligatoria: +- Identificador, inicialmente vacío y en modo lectura +- Nombre del cliente, mediante un combo seleccionable +- Nombre del juego, mediante un combo seleccionable +- Fechas del préstamo, donde se podrá introducir dos fechas, de inicio y fin del préstamo. + +Las validaciones son sencillas aunque laboriosas: +- La fecha de fin NO podrá ser anterior a la fecha de inicio +- El periodo de préstamo máximo solo podrá ser de 14 días. Si el usuario quiere un préstamo para más de 14 días la aplicación no debe permitirlo mostrando una alerta al intentar guardar. +- El mismo juego no puede estar prestado a dos clientes distintos en un mismo día. OJO que los préstamos tienen fecha de inicio y fecha fin, el juego no puede estar prestado a más de un cliente para ninguno de los días que contemplan las fechas actuales del rango. +- Un mismo cliente no puede tener prestados más de 2 juegos en un mismo día. OJO que los préstamos tienen fecha de inicio y fecha fin, el cliente no puede tener más de dos préstamos para ninguno de los días que contemplan las fechas actuales del rango. + +Para empezar te daré unos consejos: + +- Recuerda crear la tabla de la BBDD y sus datos +- Intenta primero hacer el listado paginado sin filtros, en el orden que más te guste: frontend o backend. Recuerda que se trata de un listado paginado, así que deberás utilizar el objeto Page. +- Completa el listado conectando ambas capas. +- Ahora implementa los filtros, presta atención al filtro de fecha, es el más complejo. +- Para la paginación filtrada solo tienes que mezclar los conceptos que hemos visto en los puntos del tutorial anteriores. +- Si hiciste el backend en Springboot recuerda revisar Baeldung por si tienes dudas sobre las queries y recuerda que las Specifications son muy útiles, pero en este caso deberás implementar otro tipo de operaciones, no te sirve solo con la operación de igualdad :, que ya vimos en el tutorial. +- Implementa la pantalla de alta de préstamo, sin ninguna validación. +- Cuando ya te funcione, intenta ir añadiendo una a una las validaciones. Algunas de ellas pueden hacerse en frontend, mientras que otras deberán validarse en backend +- Os recordamos que han de poder crearse y editarse préstamos según las reglas de validación indicadas anteriormente. Aplican las mismas reglas para ambas operaciones. +- El Backend ha de validar siempre, independientemente de que el Frontend ya lo haya validado. Nunca confíes de manera exclusiva en terceras partes (Frontend o en otro Backend). + + + + +Te voy a dar otras directrices que pienso que te pueden servir: +- Se necesita un CRUD de prestamos +- Debe tener una búsqueda y una paginación, así que fíjate en como está hecho en otras pantallas +- Todo lo que se pueda tendrá que estar con componentes de tipo dropdown +- Es posible que tengas que implementar algún nuevo endpoint para rellenar los componentes dropdown, diseña eso también para el backend. + + +Necesito que definas: + +1. Descripción de la funcionalidad + +2. Reglas de negocio + +3. Diseño frontend: +- Componentes necesarios +- Flujos de interacción (listado, abrir modal, guardar borrar) + +4. Uso de endpoints para llamar a backend + +5. Decisiones técnicas: +- Qué patrones del sistema actual se reutilizan + + +NO implementes código. +NO analices de nuevo el proyecto. +Basa la propuesta en los patrones detectados en la fase Explore. +Haz la propuesta únicamente de frontend. +Olvídate de los test, en frontend no tenemos tests. +Añade el nuevo punto de menú en el header para que se pueda acceder. +No te inventes estilos, respeta los estilos de las pantallas (anchuras, alturas, colores, disposición de las tablas). +Utiliza los componentes de Angular Material para todo lo que puedas, no componentes nativos del navegador. + +Tendrás que escribir los ficheros de proposal, design, spec y tasks en la propuesta correspondiente. + + +``` + +Aquí es importante destacar que debe tener en cuenta: + +- debe coger el contexto generado anteriormente +- además, le debe sumar el contexto del último cambio de backend con los endpoints +- debe respetar estilos y componentes de Angular material y no inventar +- debe revisar como se rellenan los dropdown + +De nuevo este comando genera dentro del directorio ``changes`` la propuesta correspondiente, que incluye los siguientes ficheros: `proposal.md`, `design.md`, `spec.md`, `tasks.md`. Que deberemos revisar. + + +!!! tip "No nos cansaremos de decirlo" + Esta es la fase más importante, aquí es donde debes revisar toda la propuesta y si algo no te encaja o es erróneo deberías comentarlo mediante el chat o corregirlo de forma manual en el fichero que corresponda. Es tú responsabilidad. + +Una vez estemos de acuerdo con la propuesta que nos ha hecho la IA, podemos pasar al siguiente punto. + + +### Apply + +Una vez validado todo, pasamos a ejecutarlo. + +**📜 Prompt** + +Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguiente prompt: + +``` +/opsx:apply +``` + +El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado tanto en la carpeta ``backend`` como en la carpeta ``frontend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. + +### Pruebas + +Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. + +Arranca el **backend** y el **frontend** y verifica: + +- La aplicación levanta correctamente +- Las nuevas funcionalidades añadidas están accesibles +- Los flujos principales definidos en `spec.md` funcionan como se espera + +!!! warning "Ojo no te fies" + Ojo no te fies de todo lo que construya la IA. Tu estás al mando, tu debes decidir si el sistema está correctamente implementado o no. Es tu responsabilidad. + +Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, es el momento de escribirlo por el chat indicándole exactamente que es lo que falta. Cuanto más preciso y conciso seas, mejor implementará la IA. + +### Archive + +Y llegamos a la última etapa que nos define Open Spec, donde se archiva el cambio y se da por finalizada la funcionalidad. + +El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. + +--- + +**📜 Prompt** + +De nuevo nos vamos al chat de ``Visual Studio Code`` el siguiente prompt: +``` +/opsx:archive +``` -Dividir las specs en dos proposes (uno para back y uno para front) +Durante el proceso de Archive, el sistema solicitará confirmación para sincronizar los requisitos antes de archivar el cambio. -Hacer todo el back primero y luego todo el front +Recuerda que al sincronizar, los requisitos definidos en `spec.md` pasan de ser un cambio temporal a formar parte permanente del sistema. -Un último punto de probar a levantar y probar la app --> además pueden comparar con su código del ejercicio \ No newline at end of file +Si no se sincroniza, el código queda implementado, pero los requisitos no se registran en los specs principales afectando a la trazabilidad y futuras evoluciones del sistema. \ No newline at end of file diff --git a/docs/specs/loans_paid.md b/docs/specs/loans_paid.md index 750cc51..2f48e29 100644 --- a/docs/specs/loans_paid.md +++ b/docs/specs/loans_paid.md @@ -59,6 +59,9 @@ Seguiremos el ciclo completo de Open Spec: 4. Archive ``` +Aunque no es obligatorio, es altamente recomendable volver a ejecutar la fase de **``Explore``**. El sistema ha podido cambiar desde tu último cambio, alguien ha podido hacer modificaciones, etc. En tu caso no sería necesario ya que estás trabajando tu solo y no has cambiado nada, pero es buena práctica hacerlo siempre. + + ### Explore El objetivo de esta fase es **analizar el sistema existente**, sin modificar nada. @@ -359,7 +362,7 @@ Basa la propuesta en los patrones detectados en la fase Explore. ``` -Igual que en la gestión de clientes, este comando genera dentro del directorio ``changes`` la propuesta correspondiente, que incluye los siguientes ficheros: `proposal.md`, `design.md`, `spec.md`, `tasks.md` +Igual que en la gestión de clientes, este comando genera dentro del directorio ``changes`` la propuesta correspondiente, que incluye los siguientes ficheros: `proposal.md`, `design.md`, `spec.md`, `tasks.md`. Estos artefactos están adaptados a la funcionalidad de **gestión de préstamos**, incorporando las reglas de negocio, filtros y validaciones específicas de este caso de uso. @@ -393,7 +396,7 @@ Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguien El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado tanto en la carpeta ``backend`` como en la carpeta ``frontend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. -## Pruebas +### Pruebas Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. From ace541cc48eb01ad2f06ef55cf35b7b39c7a570a Mon Sep 17 00:00:00 2001 From: lauramgll <156073035+lauramgll@users.noreply.github.com> Date: Wed, 22 Apr 2026 11:50:27 +0200 Subject: [PATCH 11/13] =?UTF-8?q?Instalaci=C3=B3n=20(primera=20parte)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/specs/install.md | 87 +++++++++++++++++++++++++++++++++++----- docs/specs/loans_free.md | 10 ++--- 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/docs/specs/install.md b/docs/specs/install.md index d1960b7..ec56194 100644 --- a/docs/specs/install.md +++ b/docs/specs/install.md @@ -1,21 +1,90 @@ # Instalación del entorno !!! warning Atención - Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente + Esta sección se encuentra en desarrollo 🚧. + **NO se recomienda realizarla** a menos que te lo hayan indicado expresamente. +En esta sección se asume que ya se ha completado el tutorial base y que el **Entorno de desarrollo** para Angular y Spring Boot está correctamente configurado. + +Por tanto, partimos de un entorno en el que ya están instaladas las herramientas básicas necesarias para trabajar en el proyecto. + +Daremos por hecho que ya dispones de: + +- **Visual Studio Code** +- **Node.js** +- **Angular CLI** +- **Java (17 o superior)** + +Estas herramientas se consideran **prerrequisitos** y no se describirá de nuevo su instalación detallada en este apartado. + +!!! info "Info" + Si alguna de estas herramientas no está instalada o necesitas revisar el proceso completo de configuración, puedes consultar los siguientes apartados del tutorial: + + - Entorno de desarrollo – Angular + - Entorno de desarrollo – Spring Boot + +--- + +## Instalación de herramientas + +En este apartado se describen las herramientas necesarias y la preparación del entorno para poder trabajar con **Spec-Driven Development** utilizando **OpenSpec**. + +Durante todo el proceso se trabajará desde **Visual Studio Code**, utilizando un único workspace que contendrá el frontend, el backend y las especificaciones. + +--- + +### Verificación de Node.js + +OpenSpec se distribuye como una herramienta basada en Node.js, por lo que es necesario tener instalado **Node.js 20.19.0 o superior**. + +Para comprobar la versión instalada, ejecuta en una terminal: + +``` +node --version +``` + +Si no tienes Node.js instalado o tu versión es inferior, puedes descargarlo desde su [web oficial](https://nodejs.org/). + +Se recomienda instalar la versión LTS más reciente. + +Si tienes restricciones de permisos en el portátil, también es posible instalar Node.js a través del Portal de Empresa, siguiendo el mismo procedimiento utilizado durante la configuración del Entorno de desarrollo para el tutorial: + +1. Accede al Portal de Empresa +2. Entra en el catálogo de aplicaciones pre‑aprobadas +3. Busca Node.js +4. Instálalo desde ahí + +Una vez finalizada la instalación, vuelve a ejecutar el comando `node --version` para verificar que Node.js está correctamente instalado. + +--- + +### Instalación de OpenSpec + + +OpenSpec se puede instalar de forma global utilizando cualquiera de los gestores de paquetes soportados por Node.js. + +Si utilizas **npm**, ejecuta el siguiente comando: + +``` +npm install -g @fission-ai/openspec@latest +``` + +!!! info "Info" + OpenSpec también es compatible con otros gestores de paquetes como pnpm, yarn o bun. + + En esta guía se utilizará npm por simplicidad y porque es el más común. + +Una vez finalizada la instalación, verifica que OpenSpec está correctamente instalado ejecutando: +``` +openspec --version +``` + +Si el comando responde correctamente mostrando la versión instalada, el entorno ya está preparado para trabajar con Spec‑Driven Development utilizando OpenSpec. -Explicar lo que tenemos que tener instalado -VisualStudioCode -Node -Openspec -Luego explicar que vamos a trabajar desde VisualStudio code y por tanto necesitamos un directorio con: -- backend -- frontend -- specs Los proyectos base se deben descargar de aquí --> https://github.com/ccsw-csd/tutorial-proyectos diff --git a/docs/specs/loans_free.md b/docs/specs/loans_free.md index 585b36e..26bb312 100644 --- a/docs/specs/loans_free.md +++ b/docs/specs/loans_free.md @@ -51,7 +51,7 @@ Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ## Desarrollo de la funcionalidad -Seguiremos el ciclo completo de Open Spec: +Seguiremos el ciclo completo de : ``` 1. Explore @@ -369,7 +369,7 @@ El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posi ### Pruebas del backend -Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. +Un paso que no pertenece a OpenSpec pero que es altamente recomendable es probar los cambios realizados. Arranca el backend y verifica: - Que el servidor levanta @@ -387,7 +387,7 @@ Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, e ### Archive -Y llegamos a la última etapa que nos define Open Spec, donde se archiva el cambio y se da por finalizada la funcionalidad. +Y llegamos a la última etapa que nos define OpenSpec, donde se archiva el cambio y se da por finalizada la funcionalidad. El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. @@ -623,7 +623,7 @@ El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posi ### Pruebas -Un paso que no pertenece a Open Spec pero que es altamente recomendable es probar los cambios realizados. +Un paso que no pertenece a OpenSpec pero que es altamente recomendable es probar los cambios realizados. Arranca el **backend** y el **frontend** y verifica: @@ -638,7 +638,7 @@ Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, e ### Archive -Y llegamos a la última etapa que nos define Open Spec, donde se archiva el cambio y se da por finalizada la funcionalidad. +Y llegamos a la última etapa que nos define OpenSpec, donde se archiva el cambio y se da por finalizada la funcionalidad. El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. From fb345e08caac62c1ce24d172c3aeeca036f00236 Mon Sep 17 00:00:00 2001 From: Pablo Jimenez Date: Wed, 22 Apr 2026 12:46:00 +0200 Subject: [PATCH 12/13] Finalizado el tutorial IA v1.0 --- docs/specs/customers_free.md | 29 +++++--------------- docs/specs/customers_paid.md | 23 ++-------------- docs/specs/intro.md | 3 +++ docs/specs/{install.md => prepare.md} | 38 +++++++++++++++++++++------ mkdocs.yml | 2 +- 5 files changed, 43 insertions(+), 52 deletions(-) rename docs/specs/{install.md => prepare.md} (59%) diff --git a/docs/specs/customers_free.md b/docs/specs/customers_free.md index 470ca95..3d41d8e 100644 --- a/docs/specs/customers_free.md +++ b/docs/specs/customers_free.md @@ -6,28 +6,9 @@ ## Punto de partida -Si has llegado hasta aquí, entiendo que ya has leído tanto la introducción como la instalación del entorno. A partir de ahora voy a dar por hecho que partimos todos desde el mismo punto. +Si has llegado hasta aquí, entiendo que ya has leído tanto la introducción como la instalación del entorno. A partir de ahora voy a dar por hecho que partimos todos desde el mismo punto y que tenemos ya la estructura de directorios creada y los proyectos descargados. -Recuerda que trabajaremos desde el estado inicial del ejercicio **“Ahora hazlo tú!”**. Si no tienes el código exactamente en ese punto, no pasa nada: puedes **descargarlo desde aquí**: -[https://github.com/ccsw-csd/tutorial-proyectos](https://github.com/ccsw-csd/tutorial-proyectos) - -Una vez descargado, elige el **backend** y el **frontend** que prefieras. En este tutorial, por motivos didácticos, yo utilizaré: - -- ``server-springboot`` -- ``client-angular17`` - -## Estructura inicial del proyecto - -Crearemos un directorio general que contendrá ambos proyectos. Para simplificar el tutorial, durante todo el documento los llamaremos: - -- **``backend``** -- **``frontend``** - -La estructura debería ser similar a esta: - -![workspace](../assets/images/specs-install_1.png) - -Desde consola (o desde el terminal de tu IDE), nos situamos en el **directorio raíz** y lanzamos el inicializador de OpenSpec. +Dicho esto, nos vamos a la consola (o desde el terminal de tu IDE), nos situamos en el **directorio raíz** y lanzamos el inicializador de OpenSpec. ``` openspec init @@ -45,8 +26,12 @@ Vamos a trabajar con un **modelo gratuito**, así que es importante tener claras - El contexto es **muy limitado** - El número de operaciones mensuales también lo es +- El número de operaciones por hora también + +Así que, te pido paciencia ya que posiblemente no puedas hacer todo el tutorial completo en el mismo día, deberás trocearlo por exceso de límite de peticiones. + -Para compensar esto, vamos a: +Para intengar mitigar un poco esto, vamos a: - Dividir el trabajo en tareas pequeñas - Ser muy explícitos en los prompts diff --git a/docs/specs/customers_paid.md b/docs/specs/customers_paid.md index 7fbd043..3b29e1e 100644 --- a/docs/specs/customers_paid.md +++ b/docs/specs/customers_paid.md @@ -6,28 +6,9 @@ ## Punto de partida -Si has llegado hasta aquí, entiendo que ya has leído tanto la introducción como la instalación del entorno. A partir de ahora voy a dar por hecho que partimos todos desde el mismo punto. +Si has llegado hasta aquí, entiendo que ya has leído tanto la introducción como la instalación del entorno. A partir de ahora voy a dar por hecho que partimos todos desde el mismo punto y que tenemos ya la estructura de directorios creada y los proyectos descargados. -Recuerda que trabajaremos desde el estado inicial del ejercicio **“Ahora hazlo tú!”**. Si no tienes el código exactamente en ese punto, no pasa nada: puedes **descargarlo desde aquí**: -[https://github.com/ccsw-csd/tutorial-proyectos](https://github.com/ccsw-csd/tutorial-proyectos) - -Una vez descargado, elige el **backend** y el **frontend** que prefieras. En este tutorial, por motivos didácticos, yo utilizaré: - -- ``server-springboot`` -- ``client-angular17`` - -## Estructura inicial del proyecto - -Crearemos un directorio general que contendrá ambos proyectos. Para simplificar el tutorial, durante todo el documento los llamaremos: - -- **``backend``** -- **``frontend``** - -La estructura debería ser similar a esta: - -![workspace](../assets/images/specs-install_1.png) - -Desde consola (o desde el terminal de tu IDE), nos situamos en el **directorio raíz** y lanzamos el inicializador de OpenSpec. +Dicho esto, nos vamos a la consola (o desde el terminal de tu IDE), nos situamos en el **directorio raíz** y lanzamos el inicializador de OpenSpec. ``` openspec init diff --git a/docs/specs/intro.md b/docs/specs/intro.md index 7de0030..acf86bf 100644 --- a/docs/specs/intro.md +++ b/docs/specs/intro.md @@ -60,6 +60,9 @@ A lo largo de esta guía se utilizará OpenSpec como marco de trabajo para aplic OpenSpec traslada el foco a las especificaciones, que definen explícitamente el comportamiento esperado del sistema y sirven como contrato para la IA, reduciendo ambigüedades y asegurando trazabilidad entre lo definido y lo implementado. +Es **sumamente importante** que se defina de forma concreta y muy concisa los requisitos y las reglas que debe seguir la IA a la hora de analizar y generar. + + ## Fases de OpenSpec El flujo de trabajo de OpenSpec se estructura en cuatro fases principales: diff --git a/docs/specs/install.md b/docs/specs/prepare.md similarity index 59% rename from docs/specs/install.md rename to docs/specs/prepare.md index ec56194..63887dd 100644 --- a/docs/specs/install.md +++ b/docs/specs/prepare.md @@ -1,4 +1,4 @@ -# Instalación del entorno +# Preparación del entorno !!! warning Atención Esta sección se encuentra en desarrollo 🚧. @@ -6,6 +6,8 @@ En esta sección se asume que ya se ha completado el tutorial base y que el **Entorno de desarrollo** para Angular y Spring Boot está correctamente configurado. +Además, sería de mucha ayuda si además has realizado el ejercicio **`Ahora hazlo tu!`** ya que así tendrás el conocimiento de lo que estamos intentando construir en este punto. + Por tanto, partimos de un entorno en el que ya están instaladas las herramientas básicas necesarias para trabajar en el proyecto. Daremos por hecho que ya dispones de: @@ -83,20 +85,40 @@ Si el comando responde correctamente mostrando la versión instalada, el entorno +## Preparación de entorno + +### Github Copilot + +Llegados a este punto, y para poder seguir, necesitas una cuenta de GitHub con GitHub Copilot, da igual que sea con licencia premium o con versión gratuita. Además de tener cuenta, deberás acceder desde ``Visual Studio Code`` a esta cuenta para poder activar las características del chat. + + +### Estructura inicial del proyecto + +A partir de aquí, necesitas los proyectos base (*sin el ejercicio hecho*) para poder seguir con los siguientes puntos de la guía. +Si no los tienes, los puedes descargar de aquí [https://github.com/ccsw-csd/tutorial-proyectos](https://github.com/ccsw-csd/tutorial-proyectos). + + +En nuestro ejemplo vamos a utilizar los proyectos de ``server-springboot`` y ``client-angular17``. Los dos proyectos deberían estar en un mismo directorio raiz. Para simplificar los siguientes puntos, durante todo el documento, los llamaremos: +- **``backend``** +- **``frontend``** +La estructura debería ser similar a esta: -Los proyectos base se deben descargar de aquí --> https://github.com/ccsw-csd/tutorial-proyectos +![workspace](../assets/images/specs-install_1.png) -para el ejemplo vamos a usar angular y springboot +### ¿Y ahora qué? +Pues a partir de ahora debemos elegir que camino tomar... si tenemos GitHub Copilot premium con licencia, podemos hacer el tutorial versión 💰💰 que tendrá más contexto y será mucho más rápido y concreto. -Luego contar que hay modelos de pago y modelos gratuitos y por tanto deben elegir una de las opciones. Contar beneficios de uno y de otro +Si por el contrario tenemos la licencia gratuita de GitHub Copilot, podremos hacer la versión del tutorial 🆓🆓, pero será bastante más lento y deberemos abordarlo de otra forma. Además de armarnos de mucha paciencia. +La versión gratuita tiene muchas limitaciones, de contexto, de peticiones por hora y por día que es posible que superes y tengas que esperar al día siguiente para continuar con el tutorial. -- 🆓 Gestión de clientes: specs/customers_free.md -- 🆓 Gestión de préstamos: specs/loans_free.md +Tu decides: -- 💰 Gestión de clientes: specs/customers_paid.md -- 💰 Gestión de préstamos: specs/loans_paid.md +- [🆓 Gestión de clientes](./customers_free.md) +- [🆓 Gestión de préstamos](./loans_free.md) +- [💰 Gestión de clientes](./customers_paid.md) +- [💰 Gestión de préstamos](./loans_paid.md) diff --git a/mkdocs.yml b/mkdocs.yml index f01bc6d..516a8ce 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,7 +40,7 @@ nav: - Ahora hazlo tú!: exercise.md - Spec Driven Development: - Introducción: specs/intro.md - - Instalación entorno: specs/install.md + - Preparación entorno: specs/prepare.md - 🆓 Gestión de clientes: specs/customers_free.md - 🆓 Gestión de préstamos: specs/loans_free.md - 💰 Gestión de clientes: specs/customers_paid.md From b5c7a5eee639dabfb34517a0030012ea3c10831f Mon Sep 17 00:00:00 2001 From: Pablo Jimenez Date: Wed, 22 Apr 2026 15:33:04 +0200 Subject: [PATCH 13/13] Refactor homogeneo --- docs/specs/customers_free.md | 136 ++++++----------------------------- docs/specs/customers_paid.md | 125 +++++--------------------------- docs/specs/intro.md | 122 +++++++++++++++++++++++++++++-- docs/specs/loans_free.md | 20 ++++-- docs/specs/loans_paid.md | 20 ++++-- docs/specs/prepare.md | 57 ++++++++------- 6 files changed, 214 insertions(+), 266 deletions(-) diff --git a/docs/specs/customers_free.md b/docs/specs/customers_free.md index 3d41d8e..8d9f858 100644 --- a/docs/specs/customers_free.md +++ b/docs/specs/customers_free.md @@ -20,7 +20,16 @@ Seleccionamos **``GitHub Copilot``**, pulsamos **``Enter``** para añadirlo y de Esto instalará las plantillas necesarias para poder trabajar con **OpenSpec + GitHub Copilot**. -## Consejos antes de empezar +## Requisitos funcionales + +- CRUD de clientes. +- Entidad cliente con `id` y `name`. +- Listado simple sin filtros ni paginación. +- Alta/edición en modal. +- El nombre es el único campo editable. +- No se permite guardar clientes con nombre duplicado. + +## Estrategia del modo gratuito Vamos a trabajar con un **modelo gratuito**, así que es importante tener claras sus limitaciones: @@ -50,10 +59,6 @@ El modelo que utilizaremos será **``Claude Haiku``**. Para ello debes: En cualquier momento puedes ver el consumo mensual de tu cuenta pulsando el icono de la rana 🐸 en la esquina inferior derecha. El contador **se reinicia cada mes**. - - -## Estrategia de trabajo - Vamos a dividir el ejercicio en **dos grandes bloques**: 1. Primero trabajaremos únicamente con el **``backend``** @@ -63,9 +68,9 @@ De esta forma limitamos el contexto a un solo proyecto y facilitamos el trabajo Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ti te genera algo diferente a lo que ves aquí, probablemente seguirá siendo válido. No te frustres y ajusta los prompts si es necesario. -## Generación de backend +## Flujo de trabajo OpenSpec -Seguiremos el ciclo completo de OpenSpec: +Seguiremos el ciclo completo de : ``` 1. Explore @@ -74,6 +79,8 @@ Seguiremos el ciclo completo de OpenSpec: 4. Archive ``` +## Generación de backend + ### Explore El objetivo de esta fase es **analizar el sistema existente**, sin modificar nada. @@ -171,8 +178,6 @@ Escríbe el resultado del contexto dentro del fichero backend-explore.md en el d ``` -**📄 backend-explore.md** - Este comando realizará un análisis exhaustivo de tu sistema y lo dejará escrito dentro de la carpeta ``specs`` en un fichero llamado ``backend-explore.md``. Al final te mostrará un texto con el resumen del sistema y además escribirá el fichero de explore. !!! tip "Sobre los permisos" @@ -300,84 +305,12 @@ Tendrás que escribir los ficheros de proposal, design, spec y tasks en la propu ``` De nuevo al ser un modelo gratuito tenemos que delimitarle mucho las tareas y recordarle que debe generar los ficheros de ``proposal``, ``design``, ``spec`` y ``tasks``, además de basarse en el fichero de ``backend-explore``. También debemos centrarle para que **SOLO** genere la parte de backend. -Por último si te fijas en el prompt hay una tarea que le indica claramente que genere un fichero con los contratos de los endpoints para poder implementar, en un futuro, la parte frontend. Esto es solamente una idea de generar un resumen para que el frontend sepa como comunicarse con el backend. - -Este comando debería generar un directorio dentro de ``changes`` con el nombre que le hayamos puesto a la propuesta y dentro los 4 ficheros solicitados: - -![proposal](../assets/images/specs-customer-free_3.png) - -Además en el chat también hará un pequeño resumen de lo que ha propuesto como cambios. - -Veamos lo que contiene cada uno de esos ficheros. - - -**📄 proposal.md** - -Define la funcionalidad a alto nivel. - -Incluye: - -- El problema que se quiere resolver (Why) -- Qué cambios se van a introducir (What Changes) -- El alcance funcional -- El impacto en la aplicación - -Responde a: ¿Qué se va a construir y por qué? - -**📄 design.md** - -Describe el diseño técnico de la solución. - -Incluye: - -- Contexto del sistema actual -- Objetivos (Goals / Non-Goals) -- Decisiones técnicas y su justificación -- Alternativas consideradas -- Riesgos y trade-offs - -Responde a: ¿Cómo se va a construir y por qué se ha elegido este enfoque? -**📄 spec.md** - -Define el comportamiento funcional esperado. - -Incluye: - -- Requisitos funcionales -- Casos de uso expresados como escenarios (WHEN / THEN) -- Reglas de negocio -- Validaciones y restricciones - -Responde a: ¿Qué debe hacer el sistema? - -**📄 tasks.md** - -Descompone la implementación en tareas ejecutables. Quizá es el fichero más importante. - -Incluye: - -- Lista ordenada de tareas -- Pasos concretos para implementar la funcionalidad - -Responde a: ¿Cómo se implementa paso a paso? - -**Relación entre los artefactos** - -Cada uno de los ficheros generados cumple un rol específico dentro del flujo de OpenSpec: - -- **spec.md** → define el comportamiento esperado (*qué debe hacer el sistema*) -- **design.md** → define la solución técnica (*cómo se va a construir*) -- **proposal.md** → aporta contexto y alcance (*por qué se construye*) -- **tasks.md** → guía la ejecución paso a paso (*cómo se implementa*) - -Esta separación de responsabilidades permite: +Por último si te fijas en el prompt hay una tarea que le indica claramente que genere un fichero con los contratos de los endpoints para poder implementar, en un futuro, la parte frontend. Esto es solamente una idea de generar un resumen para que el frontend sepa como comunicarse con el backend. -- Evitar mezclar requisitos con implementación -- Revisar cada nivel de forma independiente -- Detectar errores e inconsistencias antes de escribir código +Este paso debe generar una propuesta dentro de `changes` con los artefactos `proposal`, `design`, `spec` y `tasks`. -Estos artefactos constituyen la base para la siguiente fase: **Apply**, donde se ejecutará la implementación siguiendo las tareas definidas. +Si necesitas recordar el papel de cada artefacto, revisa la `introducción`. !!! tip "Responsabilidades como developer IA" En este punto la IA te ha hecho una propuesta que puede ser correcta o no, recordemos que se trata de un modelo matemático-probabilístico. Si hay algo de lo propuesto que no te encaja o es erróneo deberías comentarlo mediante el chat o corregirlo de forma manual en el fichero que corresponda. Por ejemplo si quieres añadir una tarea porqué se te ha olvidado incluirla en el prompt original, deberías decirle al modelo que te incluya la nueva tarea. @@ -411,7 +344,7 @@ Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguien El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado dentro de la carpeta de ``backend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. -### Pruebas del backend +### Verificación del backend Un paso que no pertenece a OpenSpec pero que es altamente recomendable es probar los cambios realizados. Arranca el backend y verifica: @@ -455,36 +388,9 @@ De nuevo nos vamos al chat de ``Visual Studio Code`` el siguiente prompt: ``` -En ese caso, el sistema solicita confirmación para sincronizar los requisitos antes de archivar el cambio. - -**¿Qué significa sincronizar?** - -Al seleccionar la opción de sincronización: - -- Se integran los nuevos requisitos definidos en spec.md -- Se crea o actualiza el spec definitivo -- Los requisitos pasan a formar parte oficial del sistema - -Es decir, los requisitos pasan de ser un cambio temporal a formar parte permanente del sistema. - -**¿Qué ocurre si no se sincroniza?** - -Si se decide no sincronizar: - -- El código permanece implementado -- Los requisitos no se registran en los specs principales - -Esto puede provocar: - -- Pérdida de trazabilidad -- Dificultad para futuras evoluciones -- Desalineación entre código y documentación - -**Tras completar el proceso de Archive:** +En este punto el sistema pedirá confirmación para sincronizar requisitos antes de archivar. -- La funcionalidad queda documentada como completada -- El cambio deja de formar parte de los cambios activos -- Los requisitos quedan integrados definitivamente en el sistema (si se ha sincronizado) +Sincronizar es obligatorio si quieres mantener trazabilidad entre lo implementado y los specs oficiales. **📜 Actualización del contexto** @@ -655,7 +561,7 @@ Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguien El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado dentro de la carpeta de ``frontend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. -### Pruebas del frontend +### Verificación del frontend Ahora sí, prueba de 🔥 fuego 🔥. Es hora de levantar el sistema completo, ``frontend`` y ``backend``, navegar por la aplicación y comprobar que todo funciona. diff --git a/docs/specs/customers_paid.md b/docs/specs/customers_paid.md index 3b29e1e..706768e 100644 --- a/docs/specs/customers_paid.md +++ b/docs/specs/customers_paid.md @@ -20,7 +20,16 @@ Seleccionamos **``GitHub Copilot``**, pulsamos **``Enter``** para añadirlo y de Esto instalará las plantillas necesarias para poder trabajar con **OpenSpec + GitHub Copilot**. -## Consejos antes de empezar +## Requisitos funcionales + +- CRUD de clientes. +- Entidad cliente con `id` y `name`. +- Listado simple sin filtros ni paginación. +- Alta/edición en modal. +- El nombre es el único campo editable. +- No se permite guardar clientes con nombre duplicado. + +## Estrategia del modo con licencia Vamos a trabajar con un **modelo de pago**, por lo que es importante entender qué ventajas nos ofrece frente al modelo gratuito. @@ -43,8 +52,6 @@ El modelo que utilizaremos será **``Claude Sonnet 4.6``**. Para ello debes: En cualquier momento puedes ver el consumo mensual de tu cuenta pulsando el icono de la rana 🐸 en la esquina inferior derecha. El contador **se reinicia cada mes**. -## Estrategia de trabajo - Vamos a abordar el ejercicio como un **único bloque de trabajo**, analizando y construyendo la funcionalidad de forma **simultánea en backend y frontend**. De esta manera aprovechamos el **mayor contexto** del modelo de pago, permitiendo: @@ -56,7 +63,7 @@ Esto nos permite mantener una visión global del sistema durante todo el proceso Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ti te genera algo diferente a lo que ves aquí, probablemente seguirá siendo válido. No te frustres y ajusta los prompts si es necesario. -## Desarrollo de la funcionalidad +## Flujo de trabajo OpenSpec Seguiremos el ciclo completo de OpenSpec: @@ -277,82 +284,9 @@ NO analices de nuevo el proyecto. Basa la propuesta en los patrones detectados en la fase Explore. ``` -Este comando debería generar un directorio dentro de ``changes`` con el nombre que le hayamos puesto a la propuesta y dentro los siguientes ficheros: -`proposal.md`, `design.md`, `spec.md`, `tasks.md` - -![proposal](../assets/images/specs-customer-paid_4.png) - -Además en el chat también hará un pequeño resumen de lo que ha propuesto como cambios. - -Veamos lo que contiene cada uno de esos ficheros. - -**📄 proposal.md** - -Define la funcionalidad a alto nivel. - -Incluye: - -- El problema que se quiere resolver (Why) -- Qué cambios se van a introducir (What Changes) -- El alcance funcional -- El impacto en la aplicación - -Responde a: ¿Qué se va a construir y por qué? - -**📄 design.md** - -Describe el diseño técnico de la solución. - -Incluye: - -- Contexto del sistema actual -- Objetivos (Goals / Non-Goals) -- Decisiones técnicas y su justificación -- Alternativas consideradas -- Riesgos y trade-offs - -Responde a: ¿Cómo se va a construir y por qué se ha elegido este enfoque? - -**📄 spec.md** - -Define el comportamiento funcional esperado. - -Incluye: - -- Requisitos funcionales -- Casos de uso expresados como escenarios (WHEN / THEN) -- Reglas de negocio -- Validaciones y restricciones - -Responde a: ¿Qué debe hacer el sistema? +Este comando debe generar una propuesta en `changes` con los artefactos `proposal`, `design`, `spec` y `tasks`. -**📄 tasks.md** - -Descompone la implementación en tareas ejecutables. Quizá es el fichero más importante. - -Incluye: - -- Lista ordenada de tareas -- Pasos concretos para implementar la funcionalidad - -Responde a: ¿Cómo se implementa paso a paso? - -**Relación entre los artefactos** - -Cada uno de los ficheros generados cumple un rol específico dentro del flujo de OpenSpec: - -- **spec.md** → define el comportamiento esperado (*qué debe hacer el sistema*) -- **design.md** → define la solución técnica (*cómo se va a construir*) -- **proposal.md** → aporta contexto y alcance (*por qué se construye*) -- **tasks.md** → guía la ejecución paso a paso (*cómo se implementa*) - -Esta separación de responsabilidades permite: - -- Evitar mezclar requisitos con implementación -- Revisar cada nivel de forma independiente -- Detectar errores e inconsistencias antes de escribir código - -Estos artefactos constituyen la base para la siguiente fase: **Apply**, donde se ejecutará la implementación siguiendo las tareas definidas. +Si necesitas recordar el papel de cada artefacto, revisa la `introducción`. !!! tip "Responsabilidades como developer IA" En este punto la IA te ha hecho una propuesta que puede ser correcta o no, recordemos que se trata de un modelo matemático-probabilístico. Si hay algo de lo propuesto que no te encaja o es erróneo deberías comentarlo mediante el chat o corregirlo de forma manual en el fichero que corresponda. Por ejemplo si quieres añadir una tarea porqué se te ha olvidado incluirla en el prompt original, deberías decirle al modelo que te incluya la nueva tarea. @@ -382,7 +316,7 @@ Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguien El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado tanto en la carpeta ``backend`` como en la carpeta ``frontend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. -### Pruebas +### Verificación Un paso que no pertenece a OpenSpec pero que es altamente recomendable es probar los cambios realizados. @@ -423,33 +357,6 @@ De nuevo nos vamos al chat de ``Visual Studio Code`` el siguiente prompt: /opsx:archive ``` -En ese caso, el sistema solicita confirmación para sincronizar los requisitos antes de archivar el cambio. - -**¿Qué significa sincronizar?** - -Al seleccionar la opción de sincronización: - -- Se integran los nuevos requisitos definidos en spec.md -- Se crea o actualiza el spec definitivo -- Los requisitos pasan a formar parte oficial del sistema - -Es decir, los requisitos pasan de ser un cambio temporal a formar parte permanente del sistema. - -**¿Qué ocurre si no se sincroniza?** - -Si se decide no sincronizar: - -- El código permanece implementado -- Los requisitos no se registran en los specs principales - -Esto puede provocar: - -- Pérdida de trazabilidad -- Dificultad para futuras evoluciones -- Desalineación entre código y documentación - -**Tras completar el proceso de Archive:** +En este punto el sistema pedirá confirmación para sincronizar requisitos antes de archivar. -- La funcionalidad queda documentada como completada -- El cambio deja de formar parte de los cambios activos -- Los requisitos quedan integrados definitivamente en el sistema (si se ha sincronizado) \ No newline at end of file +Sincronizar es obligatorio si quieres mantener trazabilidad entre lo implementado y los specs oficiales. \ No newline at end of file diff --git a/docs/specs/intro.md b/docs/specs/intro.md index acf86bf..0c80dfe 100644 --- a/docs/specs/intro.md +++ b/docs/specs/intro.md @@ -4,9 +4,9 @@ Esta sección se encuentra en desarrollo 🚧. **NO se recomienda realizarla** a menos que te lo hayan indicado expresamente. -## Contexto de la implementación +## Contexto -En esta sección se describe la implementación de nuevas funcionalidades en la aplicación siguiendo un enfoque de **Spec-Driven Development (SDD)**. +En este bloque del tutorial vamos a implementar nuevas funcionalidades usando **Spec-Driven Development (SDD)** con **OpenSpec**. El objetivo no es únicamente desarrollar nuevas funcionalidades, sino hacerlo siguiendo un proceso estructurado que permita separar claramente las distintas fases del desarrollo y garantizar **trazabilidad entre análisis, definición, implementación y validación**. @@ -114,7 +114,77 @@ Fase orientada a la **definición de la solución** a implementar. **Resultado** -Una propuesta clara, estructurada y alineada con el objetivo del cambio, que servirá como base para su implementación. +Una propuesta clara, estructurada y alineada con el objetivo del cambio, que servirá como base para su implementación. En esta fase se deberían generar 4 ficheros. + + +**📄 proposal.md** + +Define la funcionalidad a alto nivel. + +Incluye: + +- El problema que se quiere resolver (Why) +- Qué cambios se van a introducir (What Changes) +- El alcance funcional +- El impacto en la aplicación + +Responde a: ¿Qué se va a construir y por qué? + +**📄 design.md** + +Describe el diseño técnico de la solución. + +Incluye: + +- Contexto del sistema actual +- Objetivos (Goals / Non-Goals) +- Decisiones técnicas y su justificación +- Alternativas consideradas +- Riesgos y trade-offs + +Responde a: ¿Cómo se va a construir y por qué se ha elegido este enfoque? + +**📄 spec.md** + +Define el comportamiento funcional esperado. + +Incluye: + +- Requisitos funcionales +- Casos de uso expresados como escenarios (WHEN / THEN) +- Reglas de negocio +- Validaciones y restricciones + +Responde a: ¿Qué debe hacer el sistema? + +**📄 tasks.md** + +Descompone la implementación en tareas ejecutables. Quizá es el fichero más importante. + +Incluye: + +- Lista ordenada de tareas +- Pasos concretos para implementar la funcionalidad + +Responde a: ¿Cómo se implementa paso a paso? + +**Relación entre los artefactos** + +Cada uno de los ficheros generados cumple un rol específico dentro del flujo de OpenSpec: + +- **spec.md** → define el comportamiento esperado (*qué debe hacer el sistema*) +- **design.md** → define la solución técnica (*cómo se va a construir*) +- **proposal.md** → aporta contexto y alcance (*por qué se construye*) +- **tasks.md** → guía la ejecución paso a paso (*cómo se implementa*) + +Esta separación de responsabilidades permite: + +- Evitar mezclar requisitos con implementación +- Revisar cada nivel de forma independiente +- Detectar errores e inconsistencias antes de escribir código + +Estos artefactos constituyen la base para la siguiente fase. + --- @@ -146,4 +216,48 @@ Fase final de **cierre y consolidación** del cambio. **Resultado** -Un cambio finalizado, validado y correctamente documentado. \ No newline at end of file +Un cambio finalizado, validado y correctamente documentado. +En esta fase se pedirá sincronizar los requisitos antes de archivar y consolidar. + +**¿Qué significa sincronizar?** + +Al seleccionar la opción de sincronización: + +- Se integran los nuevos requisitos definidos en spec.md +- Se crea o actualiza el spec definitivo +- Los requisitos pasan a formar parte oficial del sistema + +Es decir, los requisitos pasan de ser un cambio temporal a formar parte permanente del sistema. + +**¿Qué ocurre si no se sincroniza?** + +Si se decide no sincronizar: + +- El código permanece implementado +- Los requisitos no se registran en los specs principales + +Esto puede provocar: + +- Pérdida de trazabilidad +- Dificultad para futuras evoluciones +- Desalineación entre código y documentación + +**Tras completar el proceso de Archive:** + +- La funcionalidad queda documentada como completada +- El cambio deja de formar parte de los cambios activos +- Los requisitos quedan integrados definitivamente en el sistema (si se ha sincronizado) + + + +## Principios de calidad + +!!! tip "SDD y agentes de IA" + Con agentes de IA se genera código muy rápido, pero la responsabilidad técnica sigue siendo tuya. + +Durante todo el proceso: + +- revisa siempre la propuesta antes de aplicar, +- valida funcionalmente lo implementado, +- corrige tareas o requisitos cuando detectes desviaciones, +- no asumas que la primera respuesta de la IA es correcta. \ No newline at end of file diff --git a/docs/specs/loans_free.md b/docs/specs/loans_free.md index 26bb312..38a48c8 100644 --- a/docs/specs/loans_free.md +++ b/docs/specs/loans_free.md @@ -25,7 +25,17 @@ En este tutorial seguiremos trabajando sobre: - ``client-angular17`` como **``frontend``** -## Consejos antes de empezar +## Requisitos funcionales + +- Gestión de préstamos entre clientes y juegos. +- Listado paginado con filtros por juego, cliente y fecha. +- Alta/edición en modal con campos obligatorios (salvo identificador). +- Validaciones de fechas y restricciones de solapamiento. +- Máximo 14 días por préstamo. +- Un juego no puede estar prestado a dos clientes en el mismo día. +- Un cliente no puede tener más de dos préstamos activos en el mismo día. + +## Estrategia del modo gratuito Continuaremos trabajando con un **modelo gratuito**, utilizando **``Claude Haiku``** y el mismo workspace que en la funcionalidad de **gestión de clientes**. @@ -37,8 +47,6 @@ Esto ayuda a mantener el contexto limpio y a que el modelo se centre exclusivame Recuerda que en cualquier momento puedes ver el consumo mensual de tu cuenta pulsando el icono de la rana 🐸 en la esquina inferior derecha. El contador **se reinicia cada mes**. -## Estrategia de trabajo - Al igual que el ejercicio anterior, vamos a dividir el ejercicio en **dos grandes bloques**: 1. Primero trabajaremos únicamente con el **``backend``** @@ -49,7 +57,7 @@ De esta forma limitamos el contexto a un solo proyecto y facilitamos el trabajo Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ti te genera algo diferente a lo que ves aquí, probablemente seguirá siendo válido. No te frustres y ajusta los prompts si es necesario. -## Desarrollo de la funcionalidad +## Flujo de trabajo OpenSpec Seguiremos el ciclo completo de : @@ -367,7 +375,7 @@ Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguien El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado dentro de la carpeta de ``backend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. -### Pruebas del backend +### Verificación del backend Un paso que no pertenece a OpenSpec pero que es altamente recomendable es probar los cambios realizados. Arranca el backend y verifica: @@ -621,7 +629,7 @@ Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguien El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado tanto en la carpeta ``backend`` como en la carpeta ``frontend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. -### Pruebas +### Verificación del frontend Un paso que no pertenece a OpenSpec pero que es altamente recomendable es probar los cambios realizados. diff --git a/docs/specs/loans_paid.md b/docs/specs/loans_paid.md index f30d8b7..a09b4ad 100644 --- a/docs/specs/loans_paid.md +++ b/docs/specs/loans_paid.md @@ -23,7 +23,17 @@ En este tutorial seguiremos trabajando sobre: - ``server-springboot`` como **``backend``** - ``client-angular17`` como **``frontend``** -## Consejos antes de empezar +## Requisitos funcionales + +- Gestión de préstamos entre clientes y juegos. +- Listado paginado con filtros por juego, cliente y fecha. +- Alta/edición en modal con campos obligatorios (salvo identificador). +- Validaciones de fechas y restricciones de solapamiento. +- Máximo 14 días por préstamo. +- Un juego no puede estar prestado a dos clientes en el mismo día. +- Un cliente no puede tener más de dos préstamos activos en el mismo día. + +## Estrategia del modo con licencia Continuaremos trabajando con un **modelo de pago**, utilizando **``Claude Sonnet 4.6``** y el mismo workspace que en la funcionalidad de **gestión de clientes**. @@ -35,8 +45,6 @@ Esto ayuda a mantener el contexto limpio y a que el modelo se centre exclusivame Recuerda que en cualquier momento puedes ver el consumo mensual de tu cuenta pulsando el icono de la rana 🐸 en la esquina inferior derecha. El contador **se reinicia cada mes**. -## Estrategia de trabajo - Vamos a abordar el ejercicio como un **único bloque de trabajo**, analizando y construyendo la funcionalidad de forma **simultánea en backend y frontend**. De esta manera aprovechamos el **mayor contexto** del modelo de pago, permitiendo: @@ -48,7 +56,7 @@ Esto nos permite mantener una visión global del sistema durante todo el proceso Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ti te genera algo diferente a lo que ves aquí, probablemente seguirá siendo válido. No te frustres y ajusta los prompts si es necesario. -## Desarrollo de la funcionalidad +## Flujo de trabajo OpenSpec Seguiremos el ciclo completo de OpenSpec: @@ -59,6 +67,8 @@ Seguiremos el ciclo completo de OpenSpec: 4. Archive ``` +### Backend y frontend + Aunque no es obligatorio, es altamente recomendable volver a ejecutar la fase de **``Explore``**. El sistema ha podido cambiar desde tu último cambio, alguien ha podido hacer modificaciones, etc. En tu caso no sería necesario ya que estás trabajando tu solo y no has cambiado nada, pero es buena práctica hacerlo siempre. @@ -396,7 +406,7 @@ Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguien El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado tanto en la carpeta ``backend`` como en la carpeta ``frontend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. -### Pruebas +### Verificación Un paso que no pertenece a OpenSpec pero que es altamente recomendable es probar los cambios realizados. diff --git a/docs/specs/prepare.md b/docs/specs/prepare.md index 63887dd..43c5587 100644 --- a/docs/specs/prepare.md +++ b/docs/specs/prepare.md @@ -4,11 +4,11 @@ Esta sección se encuentra en desarrollo 🚧. **NO se recomienda realizarla** a menos que te lo hayan indicado expresamente. -En esta sección se asume que ya se ha completado el tutorial base y que el **Entorno de desarrollo** para Angular y Spring Boot está correctamente configurado. +En esta sección asumimos que ya completaste el tutorial base y que el entorno de Angular y Spring Boot está configurado. -Además, sería de mucha ayuda si además has realizado el ejercicio **`Ahora hazlo tu!`** ya que así tendrás el conocimiento de lo que estamos intentando construir en este punto. +También es recomendable haber hecho el ejercicio **`Ahora hazlo tu!`** para que el contexto funcional te resulte familiar. -Por tanto, partimos de un entorno en el que ya están instaladas las herramientas básicas necesarias para trabajar en el proyecto. +Partimos, por tanto, de un entorno con las herramientas básicas ya instaladas. Daremos por hecho que ya dispones de: @@ -17,7 +17,7 @@ Daremos por hecho que ya dispones de: - **Angular CLI** - **Java (17 o superior)** -Estas herramientas se consideran **prerrequisitos** y no se describirá de nuevo su instalación detallada en este apartado. +Estas herramientas son **prerrequisitos** y aquí no repetiremos su instalación en detalle. !!! info "Info" Si alguna de estas herramientas no está instalada o necesitas revisar el proceso completo de configuración, puedes consultar los siguientes apartados del tutorial: @@ -27,11 +27,9 @@ Estas herramientas se consideran **prerrequisitos** y no se describirá de nuevo --- -## Instalación de herramientas +## Prerrequisitos técnicos -En este apartado se describen las herramientas necesarias y la preparación del entorno para poder trabajar con **Spec-Driven Development** utilizando **OpenSpec**. - -Durante todo el proceso se trabajará desde **Visual Studio Code**, utilizando un único workspace que contendrá el frontend, el backend y las especificaciones. +Vamos a preparar el entorno para trabajar con **Spec-Driven Development** usando **OpenSpec** desde **Visual Studio Code**, en un único workspace con frontend, backend y especificaciones. --- @@ -58,12 +56,11 @@ Si tienes restricciones de permisos en el portátil, también es posible instala Una vez finalizada la instalación, vuelve a ejecutar el comando `node --version` para verificar que Node.js está correctamente instalado. ---- +--- ### Instalación de OpenSpec - -OpenSpec se puede instalar de forma global utilizando cualquiera de los gestores de paquetes soportados por Node.js. +OpenSpec se puede instalar de forma global con cualquier gestor compatible con Node.js. Si utilizas **npm**, ejecuta el siguiente comando: @@ -72,9 +69,7 @@ npm install -g @fission-ai/openspec@latest ``` !!! info "Info" - OpenSpec también es compatible con otros gestores de paquetes como pnpm, yarn o bun. - - En esta guía se utilizará npm por simplicidad y porque es el más común. + OpenSpec también es compatible con `pnpm`, `yarn` o `bun`. En esta guía usaremos `npm` por simplicidad. Una vez finalizada la instalación, verifica que OpenSpec está correctamente instalado ejecutando: ``` @@ -84,39 +79,47 @@ openspec --version Si el comando responde correctamente mostrando la versión instalada, el entorno ya está preparado para trabajar con Spec‑Driven Development utilizando OpenSpec. +## Convenciones de trabajo (aplican a todos los ejercicios) -## Preparación de entorno +### GitHub Copilot -### Github Copilot - -Llegados a este punto, y para poder seguir, necesitas una cuenta de GitHub con GitHub Copilot, da igual que sea con licencia premium o con versión gratuita. Además de tener cuenta, deberás acceder desde ``Visual Studio Code`` a esta cuenta para poder activar las características del chat. +Necesitas una cuenta de GitHub con Copilot (gratuita o premium) y haber iniciado sesión en `Visual Studio Code` para usar el chat. ### Estructura inicial del proyecto -A partir de aquí, necesitas los proyectos base (*sin el ejercicio hecho*) para poder seguir con los siguientes puntos de la guía. -Si no los tienes, los puedes descargar de aquí [https://github.com/ccsw-csd/tutorial-proyectos](https://github.com/ccsw-csd/tutorial-proyectos). +A partir de aquí necesitas los proyectos base (*sin el ejercicio hecho*). +Si no los tienes, puedes descargarlos en [https://github.com/ccsw-csd/tutorial-proyectos](https://github.com/ccsw-csd/tutorial-proyectos). -En nuestro ejemplo vamos a utilizar los proyectos de ``server-springboot`` y ``client-angular17``. Los dos proyectos deberían estar en un mismo directorio raiz. Para simplificar los siguientes puntos, durante todo el documento, los llamaremos: +En esta guía vamos a usar `server-springboot` y `client-angular17`. Ambos deben estar en el mismo directorio raíz. Para simplificar, durante todo el documento, los llamaremos: -- **``backend``** -- **``frontend``** +- **`backend`** +- **`frontend`** La estructura debería ser similar a esta: ![workspace](../assets/images/specs-install_1.png) +### Reglas generales y de ejecución + +Durante todos los ejercicios: + +- Empieza cada cambio relevante en un chat nuevo para no arrastrar contexto innecesario. +- Revisa siempre la propuesta antes de ejecutar la fase de `Apply`. +- Valida manualmente los resultados funcionales después de aplicar. + ### ¿Y ahora qué? -Pues a partir de ahora debemos elegir que camino tomar... si tenemos GitHub Copilot premium con licencia, podemos hacer el tutorial versión 💰💰 que tendrá más contexto y será mucho más rápido y concreto. +A partir de aquí eliges ruta: -Si por el contrario tenemos la licencia gratuita de GitHub Copilot, podremos hacer la versión del tutorial 🆓🆓, pero será bastante más lento y deberemos abordarlo de otra forma. Además de armarnos de mucha paciencia. +- Con licencia de pago 💰 tendrás más contexto y menos fragmentación. +- Con licencia gratuita 🆓 tendrás menos contexto y más iteraciones. Además es posible que superes las limitaciones diarias o de hora y tengas que esperar al día siguiente para continuar con el tutorial. -La versión gratuita tiene muchas limitaciones, de contexto, de peticiones por hora y por día que es posible que superes y tengas que esperar al día siguiente para continuar con el tutorial. +El flujo funcional es el mismo en ambos casos. -Tu decides: +Elige tu camino: - [🆓 Gestión de clientes](./customers_free.md) - [🆓 Gestión de préstamos](./loans_free.md)

T(J=GTJ{pjCL^f znx%sD9xPbkjr;Z4vX$`X&!4PGHX`R+#karV?mi#fPcF+3o zfe^J`yXHp*bsnm0#*Ey#;QGu=6a;y-`^ptV!&l8#P~+ONlp_wARI+?%)|#1udh5uN zBUj0mC;vaiA^Wl;6trD)lXhi>F4^eMoymgJP@}MZ4R`Ne>RH-RQ&S_a7v$kl^7ohT zk>THOi)FZR^Cr7jvB$%Qm#x3O2wGmYe{qeg#JI42b`}yBexN0i#k8^~adUH9eNUBA zT*Hq0{Q2{{{Cvu#upJ^1r9l>}fMMpvI&ZJu%ODuh5s>FB=5 z#)jctJ7!@Lpm2-6N_};8RUwJyN7}~T9?PcSeLxEGr~6ivR8&z#kLJEl2H`O`yuX#J zbL*Cfd9AMuzyQr;fz~zuJ(*NV7tFcuY0cB|3ZyAr;lFF=>C^E^G6qoAh7jcE=3`0;W{nrTr)V&eJT)lsMd z?5^(aQ&HUM4bV<{dV4WY`W22$`MSJgp3@|$G9IL43dcgSva&h`22$C6dU^exe_q<4 z6pgJfVq#(+coA8DJS+IFs3_S!F+5J0aJnaf zQ?uvC5B7zFA%=HjcQ3PXbE_@~!6wnhs)UAze@(%xWuv90{^exe5f&bv{o%t`3o_LL zU8|MfWkU3^mhr#S{j?cSk5w)}=KlGfMzkFsmYd5R9TO8Y=BMCyC<+nHw7VWry|x$9 z(t?IJjj&;XMVC$^ZI5T63Gi=iZG9nZh3hKK1dD)NNL})-2%n(AN9^a%pOc<-_x1=o zx6@4~U%_Yk+_)H_oZH7HEltnjyWaQ&Qxm`CG+dL9nh4;@w2-~(u+FYYZr@}pjaew5 zl5^+IJxNN!P1EB31;KSePR_)aAooYy{EqkQ++59YyX9o<>(|3{^KiV!bASH4?K{md z-teefZqE7S3PaX+|8pHrf7q2ddw8_J6tS7Pqv#~(efEUF?!g17=g`4|plDQ9RYeDk z)YR1U3=ZO_Y2iT^NtO3AG>%gV50d%*EloyG_Qd0EvA0y2VeG_&xw;b$>aA8<^qoB2 zN!Ux})iUCrrOZ)*fhZ`upUTTIYn31hqTao`m?^=Uos)y4&cY2cw{c!x-iWDaDRmcN`xsSD;G- z4eDTPPUcv7Vsa7#GUEGpt%GdEwWB+FdfcaMifaaH#>R|LLYdP0d)9@V+bPNnNoq~U zxy{23*S@?Gdw|N+UYnVWUSy zIb4$F$zpvzzf*FgCx?Zp|L*A6qpDRPt2K?cYLAPOv-c``86FWN6Bim9swQCgg+x~n zD$(Gl`{zG=_)u)qoi1-dgpzO?Yng105VmR~?x?&QaOKJsq|VHj!=wU3s*s=HpY{yw^&hO0xp_HRii@eS<7yo!(AcXI{O$qT+&> zm|Md%xd3zao^Dl8+uCe^nzW1zWCjMRpr8O2-1*U?QlspEsHmuSA3k8B_)MxO@yM7d zeY)V0%|D{p#3L@2#kY1F7jc^QCl>m0kFZ~*cHC{kWAR1Cpa`pEOoz3ws#Ad^lIL2v z%|G5~Zu~NGl4A6s(s~eK`3|lRa6B}W-rVFr)6*9SEFnBS#=P*J?7R&VwmI0m@D#Q+ z1dr@WZXZcy9mM+GyLTg}d*WbI-**>>r=&34zC>kcWYnsVv^%`8_U&qJUn3yXTBo=fpnfHftP4nt5V@U1 zMMu*YC;M(pU4@NQ(+W6${`}GCE|ep4mOo7^w!5{WX<8A*#l`Zq?QwCWB@+C^4!0q; z>*|;xWi7k)prGz7_WOA^#o3k_=9|_U9*;FQV-*$_CWv3Kxcm~Si$g_FKR2%A=6I_r zoQK?3W%j}bpbjx2^zs^D4a+AHp`n=t1=sDhmD|bWM{t@CCPK_%Yu7h68dX{RX4bKA zu=?RS4e-tY{o!FHG#WdJ-za>3-gcwRe(17swL6J}gM**Etd5aUj6tcj#l}=C5`;|- z2b;4O1qEre1tZv5Sy7O331sqs6sNNym=l)uTwO(*nwrc->2H{s#XlG*v}lc{f{{cC z9(NqA;*3+0Ih<%VKN7GXK9z&s69G_asx{{6lP7`j(a>e4YWK#@`^4|E_`)(GamgnR>LEBg-omZsG5r^{FWJ8aFyoZ;FxF#!pb>^kKPj@<>?6w_s)uo z#BFC@#UIg@GiIlk`@ch3W7Oi9IbKGC*NezS&-xw}d*gQIMXI3PPAk{FSKz;t}CLJ9~1(CiHijXa!>BEQs|Q zVyrI+y`t#?;-r!J|1V-na=ID+@$O?XM@L5lmm-@}SzX=S(Ln&yRKW2x%SOAx>5Z|} z$kiLb1Yvc{?J=f%P7!QxZ%+VHX3)xzj|~sUy_PO(Fy^WcbJY5h*%>_oW@hi8MR92> zt@t1Y$z<12g=Y0}X2(-BgJlnB{7;t5re~AQ+E`B4@w~PyHa0hdVMOD){)(fBJzKbe zEewz=TNo5~d%*N|W%NF~%StXTCHiyI(;>*n0I0-gZ?&}}ksAZGG*HxDQMbcSNkcO& zQGY;^?zd~eXIevh>+Ln@Fjp-s*Z{%l78>8L;QVjg2QsNaY&;}sfajnGH34VPlckI#=6P+|Ao#=_s02>6JJirkIfq%ZPp?1p z@Hi`}d9wPrzFy%PS7OazR(ZMj{?X9|QBlem8bK9BMHIrVlnxOC=7daTVckB*Mk z(bWzA@_+geyg^9f8{l zM?>AtP)7g;!obc1C~1G~?9>SWFFXQ(ZVF~RSb&t{Cy`$;;vhOlM@J<+*I7?j#!pGO zwNe9N0n@z~dGgKbnSpqz^xnAy0FeCLx!?$9Z+sX&piHX50)GAawWq%y2iPKsF;6UQ z`2&t`)}_AZQGpE&icsUPrAbp2)g4}fb#~cbx4_23Yi@1Dfr4*2ELeYhZhj@m!_BSH zu54;^wi6Wy*#H0q5-4iA8QqLbu1MtP=kvk9i3d65UCWGehQ+D;{Mq94_~3;?fIPHg z7r0H7B8+1Gn~52j+1Z#7;T;_vgCFn3*mvFJN+bX}sBL#;xOG6#e}gDhA;5I(_RrB# z5*Wt>xw0*QX;ii@rGs5^c6Sd~$!P=}+ym1xL{a`6Ip`eSIaq2M=3XS`-U( zr(wuMqS3?znW}1PYd1DK1*bRKS=w_m{0V7jXgIjJrw`^b+CA#Fn2y&Y6j~r6E)EH@ z)B8bv{53R0P&)KDGm{kcl^;R&^!E3M!Cg&2SWtC3jZ_8!B>ww$ zIq~w*$W}qkKoUknQ*&b>H^XvsrbEBno(=%2%X|+TkT%$`fY1vY0RRfyd}q9^rx&xt zkzF0KHyPJpI~E~xd;D~_V&ms6?W|9qC}rH2pXi+h) zjo6r-Jxs_sDw49P;eYzUh=Ioq`tmhgoSZnkdWDqr75aRhCp!Z-U<>a{jJW z!VcZNcaM#e^Qx&^cjV*8En~GlPoF*GD5Agf4_kh2E`}FaMTiEv^1VnX^Dd|LFc!hY znQu}}4TQxQ%ed*Voq-H8o=fWuO~UC!4@%4zu_T0|TBU0?F4xm{wx1pS*lYfb#Rc4V)EF z=l+2oK3wY9mf6i+0eU5w_$m@ZAYVWy4cB;si2}BEW@ZKnbw-)6NxJjvS8^H}yzTV2Lu68~-@J*+b#6MWt*vDuEoo_Q z*W4|6{TdrZ&dA7Tkn5{3Gpw}cQwFTzw{$rpzUqgwg#HJeX41anA|fK34)Kgq!G1@( z7`4MOv9W(iTox9x=NkPs`fBp_ck5+inE~pChGH{HI%jql2j@szsAa>u!&rb+^!4vD zYcS}rg605Y)7mWoKv8n9i;KwtyCw7)q7b_J&K)XX4LE0sXavykccjNaPylrVEa+Vu z8({0PfF9mI-K&oW0ti_iXxB8&?xR(1UphL18qFJ^r~w_rMduDJ7W#Ds<1$N1C?H{1w^Yg8n?+D9Y#DIP z$nuWKKuW+^?|XJM-u|a61?Hrusj0*$H4Tl2`=!~vG(dwxzd!~y=-b6Z_K?`m^e}2` zXRE5DU%!3J4g)d@&@}}OO$1QJ5f((atF>40)lh(Yk_V@85J3W_#FBOhYo!0)zI_{2 zE7$bu6}^jG1Q`o;^7EL#`T(U*F0?F&W9Xh)gKyz-@kkj_s8m_6b3iQs-+n%shmQ|| z2+-16?%`UM=s!tJ#2R$y8y+^wmq_WFdOr-)=EL_dCvR#U?#Ws>Fy z-!)%*UOU^FtPHrlJ#cImqK;!Nt;@Q(H%Ik`iAgLFqX4Lo-v*?HZr5fmsGLuN!8{Ic4x$|B+Le`+si9{Vh(~;`xw(lUeF7;D0|PWiduyto zsR3ky^=vg;-rx5G=zt^!uwcc81%u@d3{X&J@}5CE3RcMp0s_&|v2g##(oTYo^XtMw zEmKjx-Jnb4 zS+V>KDv}I8weaw8cBs&X1-fJQim-f}?9kQ$Lomv?6MNU161yE7(pB!g1Fa5eI-x`M zD!#rogSR$!cc}%732Kdwo{$m|H8+Of;nilF0n?LCO;mI}MLc$h-?)hhqlu?#J;M5v z6ydi?;OafLnPX>cu3kulsrK;Do6q1Q+B6IZD4;2z<#*&UYFxX9qzcL$IvAjpOiymK zrDgVDT#yf0USS~+Fx2%l-a$}LB4BeN)HqhcdY}w}TJ!j|7HfXr>1}`6j?T{J!=0sG zpweL}y>%(MLb{0VtuySbBbO+V#5Lt`H8c77Si`jl+uZ?L>T(9FDh`_l8~>5l_He_{h1 z;4`aZocB8e{=Ma}rbZSdQ8j^GPEJk>;3h2@CV!4U1u;fLATZ1008s{4)Uck`cZV>Y zCout$%2hgTzt*&<{V|=0^Pg{YnG%w>Zr#f2TQL7SZ3kCYN^Z!TsL~7ps$}+>p#VtR zXj#&5_p-X97!d)15+KCE^dO&ja&Oh)a)9zT~QtM3_Kwbb3Dvpic&5L$_`t z`XtC-*VX3t@889s61n(_NlSY-Xuq1BnaQlJHF?M#e9S%!tBoQa6 z_kqxBYziZa5F7ObEzH-??|P znxpTvh_k9WALhW=sJCL54rays_wT#WAo~?QyLMUX#lOG>FgO7ZV8rf%4X^#F`hjEf841$`a-D4$cWj z&!^SZ)YX|T_v-$`3PEUWzC{J@jR%833#ew&@UxBqs-lFra2CQwf zP^>UDs>U-zCs$NgCxBV|Xn6^w!2giTPqd=se@?S>++Gnqecx{fgo8{f>fyzI$l}%^ zHp%OUe)bpp9<{f$WWIf?a@pE@q5N!| zT4^{v_y?y&7xrFeeIw*0EAC~#*6GM%JMHkw?Qzovuh<=#Sb3QEcX!=Eul|x3{u+o# z(8~a4CZMRCUu_?KG4Fd6u-U;4UBgdnAuN474fml->cLISx4G`sgd#hq!rwaYE( zgPF{xZ5DJRn5cJT{zy}%a-GPFz`ihD_Y*KCu1Xy8gq`U11Pby3PbxQs5FO74li@$*mQxSLx=pLT$ek|nwpZlE=}U#y$V$i zFss_z3Fdia(vzq0UEV%ERwsu$2{Ecx!d>1I<$W@e2C5N!bs`2r%je@eTudK0&$p&y*Q!Houc=Yu2LVky?URyHk*QDxPrd7Q9 zBOS(S=pY!WxP3V2wP`Ck9=f}?)gO1(oB#Rt67XCzkzh?ASaznK>zOuc3f6|6?Z=-z z5qzkMXp1nqOG-&~*9tWyx?;?m2ys(YScuYZOoWhS1KRI?T!tvCTipur0MDm$ z6;62I4qBvrn#vKf4Q(9 zOd`g)t(J{TMfGu@_xr~y7Y$4!E%U*HyvhKW!!IL5cD%wVEy4?e0@LCqE-t<+rIK9& zT&f&M|2T-yiddNBe0C>FyXBid-R}z=>)R50@C(@-U<1KHLy4u4dGo!{qd(V{$7&hC zlCgLmU24Wv!+XN^K3D)ZUt+F5paxqHJj%o;85dRzcQvb%#;_+(_N zz_l$dEp410t>wVFLtR9SZBX|JiHN|jU{^7Rwu%90x3RsA25%gAED#^Y@m#B`Q8XGGO6@MiShYBq4weK*yiN@HVVoB#fFe|>AK*~x#4Vdl>tOIXEq z;CN$Vh+vITs0zo?bIOjaSg}Eb!*-62Dqdcn^-q!9`Bh+)K=c59-PE6_hk*h<4@3sI z<+f)$w_3z^!yhst`(bu=s0RxNU=ovxifVr=1LygxKTpFm2h$0PI24WT{0*#eS>IuZxDdHG;A@zt$+;Ifo zX0Gl`4k%MZzMh>817^3x_gB8b3(@=M3=IuARw9)3_Y~Vp%o{P0CJdhFgy8ykuX7#J0VKDq_wNb7(PDf5eq%jQ!xx}q z3J3@QJcBj`>P}1bS{Z?#zLr_( zwZ(M3oWu@})x4aQ zj!r-UpnYcQ<_SBE$r{&sQ$d9}VMcT)Q2Zl+EJCKX48L7EYJS5|;5oCQ93k52K;5p` zw^XU~AQ!N)vAxU7o0b%aWgs4@Hm?kNAfB=TyrLB>B7|iK1Ux`Gq`<(`4`eDcsu55q1XPvp=xwS%rn9Foh%Xo3pbJz{2$gEHV@dq2R$I#=U&_2s8u80XEP$z~F%} z7@!&>oIN6iW%zCq0M#h$@LLq&)3UO%*7x?>0n>s<5I6*l2Bk{rOVuvZxCq~kT>048 z*gm9x9Af)8VQvSiih_wL7APj*GNyqEZ3xC85`sm!_VO|gh{jjJFY~86BOd5B{S)pxT_+O z#cM-_`oc3~D<(VcbOJm61_9T{y8r(6GqHo?engEe@k(n9udaD3;cWQ%$jDV{?)vR! zIaNIZ?p3qT+l_=ZOCI<}$+jrtCLdnY7E{#zfZ3$GkBEkQVRJkYQ>@q5Hs_*(1rrr< zYTm+5$CF)4|HsPjy=|A1d_+h6Grp4dRx`N-toL8uJ;Fa2Tpn)VDoJy#KVK5%cO8-SeU{K-92Cj+nfDg~`>>BJIcxK4zUqUR4I_6V)~9K!F<;vFyJ5dSyIYSP zIPu(bj^>P={{s7Bgvgjb1+Q)mH*WU9wLd13VdF6#-7lO1q_{CFF6`yGOl!_I#~Uez z95xL%ldPGn^1phE;k|d3cbWgs5WidXwU9$HN5N`K8v@6*m$+Ljs|;PJz!xl&!WL#{ zp9u5xirQY8rHA35toa)}?si2*GTwL_K{WAGHDeEV*cg4BH_C)Bp|QRi*>rO5t9SB| zs2s$)e29MQ-6I`zDqW|PuRrN9u=PDcZ#hU83^Jr2&ER>So$PI_YMM2#aFgz{ zt}`V3LZeA}s4FiX3!nZNUP_(ZdncJ{etLX6 zN}2gZadUYxqIL9f^V2JPIzMEVSxI?njs)OMPVwrG&X|)bxY!@sHZ-&j3$ov;91I&9 zel1TfP!t13lCIkq; zBPm~h+j5?hjm}NNX`rG_%))LQI9DHXkdAWvHRTi_8&)mGAu}>JNqR>^gAn*L1T-Sb z*QQ-VVHY$u0F+!sT(63uB&B=(p#upUnV>*9Z2QNC#&$m!*wAkx-N?hey}h9Fzbhzs z{wXMAr|XrN=H+;^D_1ao6`92&0)1FmqpfI@bt!PZo-kT)-lL5X9*Hu~Z*#tN^Cn`L zgh{aG^l%9=q5s6g!I{KU*v9}2<~;L-VtLr(NQw--hXwO96ctM9vAP`|@U8*dFjyZ@ zFG-gE*ViZdv9uI#g_4t-8w&-daH!NJfN(+dy1>ni2lPC+VkDg>P@q*N{0~O$DXasW zuf#k(@4yu+Z7XP={6M3_ z4}hwg?H`(`z+_Wz0&oWBR|B^AYhEuYDf#mKdpL~RATCK#Q!_Ki0TmzF2?SeYuGu_^ z5pboT)qzBVh>8G@sh3o%w9(BJe`#zD;fsF0zCFN(faU{EJH&XmdG^cn!2@342mz{b z%)+H3s>r>2oQsQ#PhP!>20J+_5d7=^_{omsI`2-5g$S6{J%$Zq=lYX3r{31yp7Z7n z4okps$?FxsnHVUbuE{$A5Kk^F@a$>;N7+&}TJ4boPzz-a1J$itSSWDqHUnSXTI8`d z#xHOSJmhfY5QH!dGy;JPd;--#DVW=hKX3MPew7*G&JKb^iL~(khm4MK0}nZ%7Ri89 z*?%o^z$#o|V^jJ_ih^tjgDnA;RX+)wCcCy6tO^P&K)`?b@D0RbIAc1bKF z)*kbn8#mZ+Z-ZqWqzwofHYeQw@SO7;g1krqA_0iQK;A1B4eZfTgOD5n^HsUSFo!Y* z=Fwz0bouC6=;tU_+_DQ7M!B;a2P4dFZ7<#g;u4;C2-9jzkZp-Ra*zWEPj!Z@RWRj) z#2-!z)Yb|J9!fARz2^6hpaanj3HDl3L4gb7@^E?2fpJMnq6W1hMh@|?e+4HOur{rI zK9gm@A zVTwC22uj)HLNp zTmgW=nE>Qj7Go;jzs~~zI1;>rpcqG5wMLLKEj}`$_m03sjvDw%fxJiGrB`}yWI>)H zwgvQE3J}8RsGVJ1@)b0b_>_End@>sP7hK`8S|Pt+tr5ZD$Xn2~<|2VB2!z+>dx1O1 z&dgLaCD|?)PU%XB!GiSO2h(~egj>zwV%`)J3ScrA<6wD$ob@o5!wC^AUS?PVI-83L zq?~0nNIM80wZ9oo!T>|%1%HJfe(dyW|7Wrq)c{KZY6LmRmD{9P;N#2zg^fA}2MQ%{ zoC$9Du)k0P5&t_6VkrjU4P3+6F|C+@h(A;D|6>-h^zjGhLEB~s83d%~O4&fmSiVqQ z6)axEDrw-}tJ5vCgPSSa^22l3eMYHDae^u@yA0v83p=pgsJRi#m)do3w z1E+<&vx7M`lQRFW_~+At|KOirvcs_0;(heqC+TlD)QIRS5pm{zf}FFt*#>7LIv$hE z`_1pcskhd{YGHM&|Hec&*X{9+*Yzl0*U)H!Uvl=%!(krFwXs^ix4$;Fwv=^ssUc{s zyVLoH!cSU~;^H#6#37b|tdS%(+W!8VH=}R)_+ShC*vZ$bMaV4<{`FsXLPTZ()dA51 zhngh)4nNs<4AWMgM@|O;osCkI9qs=99ns!srJ*``&OX<3o@yZ|iG|q@C?gfdDtHv1 zh)CJOB5VJo%54c0&kJ-9)zsCM;2rAVuoi-S%!SP1EE!l)KKMQlsRGUl(IKI>8-IR? z14ISbHWcATftG%nmd4Kr$ot4-E}O%neg94!A(5M^mnH;A@9LQrXV#65uur z6hIX4fI_if6M4pJ4%!N$(y8P;y|x8Dg9ya%yjt?&1udAf)vS#X3?4F2#l1u^oNhU<-`))b=tgm89=}aSg!@7Erpu zkFaU!A}a~vGF-r5wMWt2Xr?2cp+^rNl2cQM@9ysU3pND>$q0kxOD6S|@e3vopuoWB z0O$DNB=H|9eksn_*`jtp-n2vw_b_uDXK%v81W*#?=U#=^lpfKq??^WI%@hsKby5gax|1 zTDghX?^yqb?dD$@RR-f{e?MS`if;Y< zo~;w0Ie>HF%-tcksMl~<1r8r}Op3$dtS1Qx$ORnF zt16u91gA%o&T<%U<0NC0tKZA>=r1FExW`c1et)sz#l{p{Z-QaRch*E zL_(k`20jO}D+JW#k}g}{hfBZ+f^!SJ!n``Brg7j}$v6Ida|AiPsg*7p_2-ZE{=q@i zEcC{I-AB>Dts%!6p_9V4wIZ%H@Dq>?E?vBMF%a@7g6kDX)$QS9>7q{+X{o_8tPJr3 zheJ2Fwm2~^fvX}kBO~K3UpYXAZho5wzw*HxLbdi;?B%t6uzHmM&_MvJq@@1C7+Ns@ zVp?tVnzC2zl|=l%JjX4g0q2)zuAL4hIS_^!O2BIApM` zd(*vjGl>wX;(WltK0DO?VJR{qqCGPA7R%$l?5i=fmCZlLQ>}N4g1*iyv9jL-s{zyj zBqW)bm^ST@Gu7Y%uXLGi3ru_={bOY&IBwxl@5~vAq=X;F&VfQl3Y_Y0x-mg$CPS!y zuosua3GdNw=jr9OLtw&Y?=)5ug&e!P+}yj8-;}&{Yr@bAGx32Yha0TWL(0^Uu>4~) zc6tWHa0{xNvL3z>97h2mp)-ZdD8=R?y$#x8G2)Cf33x_WpB9}fdZyOer*afLHehRo z^QkS+++fB7ZAFFCzIV9ugndzU{cyr3;UVrSlQU)Iu36_9)BFSWo9R7$4i6qQ1Eo9D z@6odVMQi`qCFk|yAfE_a!k$JWx@T`w8VrVsqa#4i&a`?!otw0_$pss4^b3i}qd29TZ4FGE zo2<_R1m}xVBURc*35^nD`pTI;=ebT0V2UJnrnLH}d*l%uKNhCc(HB zSf9c2_IgUVdnp~fI$mLy7Wiev;|mAfQjeBJz&rDWhRSWUN^BnQ!dK=aChKGQ8*rJm znzSF#UsI$VUe&09h!^_#(I0b&%y$KO%0T*qGX{kctliHZYyvgNHbY*@2Gl2 z{EG?6oA`THZKs~`nryv~g zf*q1QUT@Z4?@{*qO*V^z$HWhZ^{?B43kCXarRzdMjhznA(1_)j6iiZH{S)w|46gMX zdt1ua9ab_us3f5uV*xB$4I4*#r;3&iMik=E5j6d*|HdBURNKyS+O$8QqgrHxh1>rb7xxpK7A^W)6jki&pV$4N2jV)u#)}dR~e|N zI0Y9G6McofILrxN{l&u0QMOH0K#x0)R8Bfmo5H+hpro`pC2wqOybg;+ypSax0$)1O z8>(xh7ts)iQWuo4>quY=L&O{q5v!PX?WXl!vaR-*T%kO zAu3lhnL;SA{d%#8p_Hozwzm1+4-N#pjs7m%T$G2(21e&FK&>~HM*rSi2y&>x|5UcB zx(e~Xzq|cmnC!(K_)R70k4Xxnq}J=`jxzWywJHVkO6>>zpn;l93*!*E<^ zwOM6va9|1zhwGGf8Vd!D2SgZzQveZ<{LQ_4FnzD=8C0UonP%93i;dD(Ca)$x+rXGX zi&_xhYr|`V&XUz@%W-S@Mb{s;(O98pbS(0IQ+w-20WJxbNqNY7Ly@DKS5J_|15T;X zq≫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%0At778#smgc{K9rtC8VjH{EiT0X@>c) z+@yUiH31C(R-WjwSEs!BSNoS7gfU^vV}bLye&D&EA{$^NCSLy`JCCwh_O$QUA zGOhhL)EAPMM{u;6Ri>H^JLD4tiU}k2$okgRq7AA!qT8Ji<~bpnmpX39b4Hxr^8-wj z`yPu=&y72UDAGT;)Ea=G$zdn9l=H?>kQQjnt|$x?mjB;m{QoLR2>dz@^cNAd*9CE) zH$H@{7{IenDpQ!?D7Acn2>}8(>_liEL?ro?#RcA6Ja)|$%5vY2Rhq9bQo>%K_G&^N zDQZveY(70QX2j`e@Z0tPscqk&2z61@uOOX_h;?9cNBe=1oWF-+cQn8oyqbW zbX0uxy5M^JS((3N1a*Byx_o{zp0N`bdn+tB*fcjXI2PLfZ`irV$~jA>I4ZsLoJoKj z#J?=i8-Ls-MV%E!>b9Nv!H#~6SakAJE?{~Lk+HYt@l!RLhvLe?a-PE=r_4=t&R1@O zlb#9#t@izylo}GNXo;eXWQey{%*)#{Co{t+6s81dNB0l#>IFJ8gG+-SoAhacqY7i`KM25>)z^*w%$2Z@pSxZ|a6b!yPh?O-{aK}#= zfxsJY6H47A&)Sw-^Qei1fNM{KKF^rEz*52@pK*qsi-k`QN@K~N<0a(HEh42+5ShD! zQ4UCCn6M03z$*w)+Mq!BJ_ybKAAd0~-O7Z={ zxPz$>;xjS7zhc)gfM?$}lFgZI4DIpWG&i!pdv^U`fyK%TLjO%3;H(x!ysU(aV=!_B zp|>S}Yn?WHejR(Rk!qU+Ojd();@s#*3|&jRzW}wNxf={t`VTL=sF;W=tffT2F`I5k zfD4sddyBHUdckZ%SKF!RnWipuETdGdsq&lUY&s;-a)F;XJIaAFDB-CalH6+wO40hi zBl3SzOF|n=lY-DQ-r+{Py8`IkGtj7nKaKhqO=1bF literal 0 HcmV?d00001 diff --git a/docs/specs/customers_free.md b/docs/specs/customers_free.md index 7bb1803..de7883a 100644 --- a/docs/specs/customers_free.md +++ b/docs/specs/customers_free.md @@ -1,44 +1,481 @@ # Gestión de clientes (modelo gratuito) !!! warning Atención - Esta sección se encuentra en desarrollo. NO se recomienda realizarla a menos que te lo hayan dicho expresamente - + Esta sección se encuentra en desarrollo 🚧. + **NO se recomienda realizarla** a menos que te lo hayan indicado expresamente. ## Punto de partida -Si has llegado hasta aquí entiendo que has leído la introducción de lo que vamos a hacer y la instalación del entorno. +Si has llegado hasta aquí, entiendo que ya has leído tanto la introducción como la instalación del entorno. A partir de ahora voy a dar por hecho que partimos todos desde el mismo punto. + +Recuerda que trabajaremos desde el estado inicial del ejercicio **“Ahora hazlo tú!”**. Si no tienes el código exactamente en ese punto, no pasa nada: puedes **descargarlo desde aquí**: +[https://github.com/ccsw-csd/tutorial-proyectos](https://github.com/ccsw-csd/tutorial-proyectos) + +Una vez descargado, elige el **backend** y el **frontend** que prefieras. En este tutorial, por motivos didácticos, yo utilizaré: -Recuerda que partiremos del tutorial en el punto inicial del ejercicio "Ahora hazlo tú!". Si no tienes el código fuente en dicho punto te lo puedes [descargar de aquí](https://github.com/ccsw-csd/tutorial-proyectos), y coger el backend y el frontend que quieras para hacer el ejercicio. +- ``server-springboot`` +- ``client-angular17`` -Nosotros con fines didacticos partiremos del tutorial de "server-springboot" y "client-angular17". +## Estructura inicial del proyecto +Crearemos un directorio general que contendrá ambos proyectos. Para simplificar el tutorial, durante todo el documento los llamaremos: -Crearemos un directorio general que contendrá las dos carpetas, por comodidad durante el tutorial las llamaremos **```backend```** y **```frontend```**. Debería quedar algo así: +- **``backend``** +- **``frontend``** + +La estructura debería ser similar a esta: ![workspace](../assets/images/specs-install_1.png) -Desde consola o desde un terminal, nos situaremos en ese directorio raiz y lanzaremos el inicializador de open specs. Escribiremos el siguiente comando: +Desde consola (o desde el terminal de tu IDE), nos situamos en el **directorio raíz** y lanzamos el inicializador de Open Specs. ``` openspec init ``` -Elegiremos de la lista que nos aparece ```GitHub Copilot```, pulsaremos **```Enter```** para añadirlo y luego **```Tab```** para validar la selección. +Seleccionamos **``GitHub Copilot``**, pulsamos **``Enter``** para añadirlo y después **``Tab``** para validar la selección. ![workspace](../assets/images/specs-install_2.png) +Esto instalará las plantillas necesarias para poder trabajar con **Open Specs + GitHub Copilot**. + +## Consejos antes de empezar + +Vamos a trabajar con un **modelo gratuito**, así que es importante tener claras sus limitaciones: + +- El contexto es **muy limitado** +- El número de operaciones mensuales también lo es + +Para compensar esto, vamos a: + +- Dividir el trabajo en tareas pequeñas +- Ser muy explícitos en los prompts +- Dar más contexto “artificial” al modelo + +> Con un modelo de pago podríamos plantear el ejercicio de forma más generalista y con menos fragmentación. + +El modelo que utilizaremos será **``Claude Haiku``**. Para ello debes: + +1. Hacer login con tu cuenta de GitHub +2. Activar GitHub Copilot +3. En el chat, abrir el tercer desplegable +4. Seleccionar el modelo **Claude Haiku** + +![Claude Haiku](../assets/images/specs-customer-free_1.png) + +En cualquier momento puedes ver el consumo mensual de tu cuenta pulsando el icono de la rana 🐸 en la esquina inferior derecha. El contador **se reinicia cada mes**. + + + +## Estrategia de trabajo + +Vamos a dividir el ejercicio en **dos grandes bloques**: + +1. Primero trabajaremos únicamente con el **``backend``** +2. Después abordaremos el **``frontend``** + +De esta forma limitamos el contexto a un solo proyecto y facilitamos el trabajo al modelo. + +Además, recuerda que el comportamiento del modelo **no es determinista**. Si a ti te genera algo diferente a lo que ves aquí, probablemente seguirá siendo válido. No te frustres y ajusta los prompts si es necesario. + +## Generación de backend + +Seguiremos el ciclo completo de Open Specs: + +``` +1. Explore +2. Propose +3. Apply +4. Archive +``` + +### Explore + +El objetivo de esta fase es **analizar el sistema existente**, sin modificar nada. + +Buscamos: + +- Entender la estructura actual +- Identificar patrones reutilizables +- Comprender cómo se comunican frontend y backend + +Aspectos a revisar: + +**Organización por dominios** + +- Estructura de carpetas +- Dominios existentes + +**Angular** + +- Componentes +- Servicios +- Modelos +- Routing + +**Spring Boot** + +- Controller +- Service +- Repository +- Entity +- DTO + +**Patrón CRUD** + +- Listados +- Creación / edición +- Borrado + +**Conexión frontend-backend** + +- Endpoints +- URLs +- DTOs + +**Reutilización** + +- Código común +- Patrones repetidos + +⚠️ En esta fase: + +- **NO** se escribe código +- **NO** se diseña la solución +- **NO** se inventan estructuras nuevas + +Solo se analiza el **sistema actual**. + +#### 📜 Prompt + +Lo que haremos será escribir en el chat de ``Visual Studio Code`` escribir el comando y las instrucciones que queramos darle. ``Recuerda haber elegido Claude Haiku``. + +``` +/opsx:explore + +Analiza el proyecto actual que está en el directorio 'backend', es una aplicación Spring Boot. Una vez analizado, responde: + +1. ¿Cómo están implementados los CRUD existentes? +- Controller +- Service +- Repository + +2. ¿Qué estructura siguen los dominios? + +3. ¿Cómo se implementan las operaciones? +- Listado +- Creación/edición +- Borrado + +4. ¿Qué formato tienen los endpoints y que relación tiene con los métodos HTTP? + +5. ¿Qué patrones o estructuras comunes se repiten en los CRUD existentes? +- Clases reutilizables +- Lógica repetida +- Estructuras comunes entre dominios + +6. ¿Existen test unitarios y de integración? ¿Cómo están implementados? ¿Utiliza algo especial al arrancar o al mockear? + +Analiza únicamente la parte de backend (Spring Boot) +NO propongas soluciones. +NO diseñes nuevas funcionalidades. +Solo analiza el sistema actual. +Escríbe el resultado del contexto dentro del fichero backend-explore.md en el directorio de las specs para poder utilizarlo en siguientes fases. + +``` + +#### 📄 backend-explore.md + +Este comando realizará un análisis exhaustivo de tu sistema y lo dejará escrito dentro de la carpeta ```specs`` en un fichero llamado ``backend-explore.md``. Al final te mostrará un texto con el resumen del sistema y además escribirá el fichero de explore. + +!!! tip "Sobre los permisos" + Es posible que durante el análisis te pida permiso para hacer ciertas tareas. Le puedes ir dando permiso una a una o darle permiso en todo el workspace, eso lo dejamos a tu elección. + + +En cualquier momento puedes ver el consumo de la ventana de contexto para saber si todo el conocimiento del sistema está en memoria o no. En el icono de la gráfica de pastel que está dentro del chat en la parte superior derecha. + +![Ventana contexto](../assets/images/specs-customer-free_2.png) + + + +### Propose + +En esta fase definimos **qué vamos a construir**, basándonos estrictamente en el resultado del Explore. + +Durante esta fase debes especificar. + +**Descripción funcional** + +- Qué hace la funcionalidad +- Qué problema resuelve + +**Reglas de negocio** + +- Validaciones +- Restricciones +- Comportamientos esperados + +**Diseño backend** + +- Endpoints necesarios +- Estructura del dominio (Entity, DTO, Service, Repository) +- Tipo de operaciones (listado, creación, edición, borrado) + +**Diseño frontend** + +- Componentes necesarios +- Flujo de usuario (listado, abrir modal, guardar, borrar) +- Servicios Angular + +**Decisiones técnicas** + +- Qué patrones existentes se reutilizan +- Qué se mantiene igual que en otros dominios +- Qué diferencias introduce esta funcionalidad + +**Plan de implementación** + +- Tareas ordenadas +- Separación backend / frontend + +Aquí dejamos claro: + +- Qué funcionalidad se va a añadir +- Qué reglas de negocio existen +- Qué piezas del sistema se ven afectadas +- Qué tareas habrá que ejecutar + +⚠️ En esta fase: + +- **NO** se implementa código +- **NO** se redefine el sistema + +#### 📜 Prompt + +Para nuestro ejemplo, lo que haremos será escribir en el chat de ``Visual Studio Code`` el siguiente prompt: + +``` + +/opsx:propose manage-customers-backend -Esto nos instalará las plantillas necesarias para usar open specs con GitHub Copilot. +Define la funcionalidad de gestión de clientes basándote en el sistema actual y en los patrones identificados en la fase Explore, tienes el resultado en el fichero "backend-explore.md". +Nos han pedido esta nueva funcionalidad. +Por un lado necesita poder tener una base de datos de sus clientes. Para ello nos ha pedido que si podemos crearle una pantalla de CRUD sencilla, al igual que hicimos con las categorías donde él pueda dar de alta a sus clientes. +Nos ha pasado un esquema muy sencillo de lo que quiere, tan solo quiere guardar un listado de los nombres de sus clientes para tenerlos fichados, y nos ha hecho un par de pantallas sencillas muy similares a Categorías. -### Guión +Un listado sin filtros de ningún tipo ni paginación. -- Dividir las specs en dos proposes (uno para back y uno para front) +Un formulario de edición / alta, cuyo único dato editable sea el nombre. Además, la única restricción que nos ha pedido es que NO podamos dar de alta a un cliente con el mismo nombre que otro existente. Así que deberemos comprobar el nombre, antes de guardar el cliente. -- Hacer todo el back primero y luego todo el front +Para empezar te daré unos consejos: +- Recuerda crear la tabla de la BBDD y sus datos +- Intenta primero hacer el listado completo, en el orden que más te guste: frontend o backend. +- Completa el listado conectando ambas capas. +- Termina el caso de uso haciendo las funcionalidades de edición, nuevo y borrado. Presta atención a la validación a la hora de guardar un cliente, NO se puede guardar si el nombre ya existe. -- Un último punto de probar a levantar y probar la app --> además pueden comparar con su código del ejercicio +Te voy a dar otras directrices que pienso que te pueden servir: +- Se necesita un CRUD de clientes +- Un cliente solo tiene: id, name +- El listado será simple, sin filtros ni paginación +- Existirá un formulario de alta / edición en modal +- El único campo editable será el nombre +- No se puede crear un cliente con un nombre ya existente, será una validación obligatoria que deberemos cumplir en el guardado + +Necesito que definas: + +1. Descripción de la funcionalidad + +2. Reglas de negocio + +3. Diseño backend: +- Endpoints necesarios +- Estructura del dominio (Entity, DTO, Service, Repository) + +4. Decisiones técnicas: +- Qué patrones del sistema actual se reutilizan + + +NO implementes código. +NO analices de nuevo el proyecto. +Basa la propuesta en los patrones detectados en la fase Explore. +Haz la propuesta únicamente de backend. +Como última tarea añade al fichero de tasks generar un resumen del cambio realizado, con el contrato de los endpoints y la información necesaria para que luego el frontend pueda implementar sus llamadas de forma sencilla. + +Tendrás que escribir los ficheros de proposal, design, spec y tasks en la propuesta correspondiente. + +``` + +De nuevo al ser un modelo gratuito tenemos que delimitarle mucho las tareas y recordarle que debe generar los ficheros de ``proposal``, ``design``, ``spec`` y ``tasks``, además de basarse en el fichero de ``backend-explore``. También debemos centrarle para que **SOLO** genere la parte de backend. +Por último si te fijas en el prompt hay una tarea que le indica claramente que genere un fichero con los contratos de los endpoints para poder implementar, en un futuro, la parte frontend. Esto es solamente una idea de generar un resumen para que el frontend sepa como comunicarse con el backend. + +Este comando debería generar un directorio dentro de ``changes`` con el nombre que le hayamos puesto a la propuesta y dentro los 4 ficheros solicitados: + +![proposal](../assets/images/specs-customer-free_3.png) + +Además en el chat también hará un pequeño resumen de lo que ha propuesto como cambios. + +Veamos lo que contiene cada uno de esos ficheros. + + +#### 📄 proposal.md + +Define la funcionalidad a alto nivel. + +Incluye: + +- El problema que se quiere resolver (Why) +- Qué cambios se van a introducir (What Changes) +- El alcance funcional +- El impacto en la aplicación + +Responde a: ¿Qué se va a construir y por qué? + +#### 📄 design.md + +Describe el diseño técnico de la solución. + +Incluye: + +- Contexto del sistema actual +- Objetivos (Goals / Non-Goals) +- Decisiones técnicas y su justificación +- Alternativas consideradas +- Riesgos y trade-offs + +Responde a: ¿Cómo se va a construir y por qué se ha elegido este enfoque? + +#### 📄 spec.md + +Define el comportamiento funcional esperado. + +Incluye: + +- Requisitos funcionales +- Casos de uso expresados como escenarios (WHEN / THEN) +- Reglas de negocio +- Validaciones y restricciones + +Responde a: ¿Qué debe hacer el sistema? + +#### 📄 tasks.md + +Descompone la implementación en tareas ejecutables. Quizá es el fichero más importante. + +Incluye: + +- Lista ordenada de tareas +- Pasos concretos para implementar la funcionalidad + +Responde a: ¿Cómo se implementa paso a paso? + + +!!! tip "Responsabilidades como developer IA" + En este punto la IA te ha hecho una propuesta que puede ser correcta o no, recordemos que se trata de un modelo matemático-probabilístico. Si hay algo de lo propuesto que no te encaja o es erróneo deberías comentarlo mediante el chat o corregirlo de forma manual en el fichero que corresponda. Por ejemplo si quieres añadir una tarea porqué se te ha olvidado incluirla en el prompt original, deberías decirle al modelo que te incluya la nueva tarea. + +Una vez estemos de acuerdo con la propuesta que nos ha hecho la IA, podemos pasar al siguiente punto. + + + +### Apply + +Una vez validada la propuesta, ejecutamos la implementación: + +El objetivo de esta fase es transformar los artefactos generados (proposal.md, design.md, spec.md, tasks.md) en código funcional, asegurando que: + +- Se respetan los requisitos funcionales definidos en spec.md +- Se siguen las decisiones técnicas definidas en design.md +- Se ejecutan las tareas en el orden establecido en tasks.md + +#### 📜 Prompt + +Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguiente prompt: + +``` + +/opsx:apply + +``` + +El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado dentro de la carpeta de ``backend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. + +### Pruebas del backend + +Un paso que no pertenece a Open Specs pero que es altamente recomendable es probar los cambios realizados. +Arranca el backend y verifica: + +- Que el servidor levanta +- Que los endpoints existen y funcionan +- Que los tests pasan + +!!! warning "Ojo no te fies" + Ojo no te fies de todo lo que construya la IA. Tu estás al mando, tu debes decidir si el sistema está correctamente implementado o no. Es tu responsabilidad. + +Si **NO** estás a gusto con la implementación o se ha dejado algo por hacer, es el momento de escribirlo por el chat indicándole exactamente que es lo que falta. Cuanto más preciso y conciso seas, mejor implementará la IA. + + + + +### Archive +Y llegamos a la última etapa que nos define open spec, el último paso es archivar el cambio. + +El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. + +En esta fase se asegura que: +- La funcionalidad ha sido correctamente implementada y validada +- No existen incidencias críticas pendientes +- La documentación asociada al cambio está completa y actualizada +- Existe una trazabilidad entre requisitos, diseño e implementación + +Aunque parezca mentira, este paso es muy importante ya que nos servirá para actualizar el contexto del sistema y archivar todos los cambios para futuras consultas. + + +#### 📜 Prompt + +De nuevo nos vamos al chat de ``Visual Studio Code`` el siguiente prompt: + +``` + +/opsx:archive + +``` + +En ese caso, el sistema solicita confirmación para sincronizar los requisitos antes de archivar el cambio. + +**¿Qué significa sincronizar?** + +Al seleccionar la opción de sincronización: + +- Se integran los nuevos requisitos definidos en spec.md +- Se crea o actualiza el spec definitivo +- Los requisitos pasan a formar parte oficial del sistema + +Es decir, los requisitos pasan de ser un cambio temporal a formar parte permanente del sistema. + +**¿Qué ocurre si no se sincroniza?** + +Si se decide no sincronizar: + +- El código permanece implementado +- Los requisitos no se registran en los specs principales + +Esto puede provocar: + +- Pérdida de trazabilidad +- Dificultad para futuras evoluciones +- Desalineación entre código y documentación + +#### 📜 Actualización + + +Además, para forzar al ``modelo gratuito`` y dejarlo todo listo, es recomendable lanzar un último prompt que nos actualice el fichero de `backend-explore.md` + +``` + +Actualiza el fichero de backend-explore con los nuevos datos implementados + +``` From 44bec05c5c46733ac238744e3166ef4a582e3e5b Mon Sep 17 00:00:00 2001 From: Pablo Jimenez Date: Tue, 21 Apr 2026 09:31:24 +0200 Subject: [PATCH 06/13] primer ejercicio free acabado --- docs/specs/customers_free.md | 207 +++++++++++++++++++++++++++++++++-- 1 file changed, 196 insertions(+), 11 deletions(-) diff --git a/docs/specs/customers_free.md b/docs/specs/customers_free.md index de7883a..7da7754 100644 --- a/docs/specs/customers_free.md +++ b/docs/specs/customers_free.md @@ -146,7 +146,7 @@ Aspectos a revisar: Solo se analiza el **sistema actual**. -#### 📜 Prompt +**📜 Prompt** Lo que haremos será escribir en el chat de ``Visual Studio Code`` escribir el comando y las instrucciones que queramos darle. ``Recuerda haber elegido Claude Haiku``. @@ -184,9 +184,9 @@ Escríbe el resultado del contexto dentro del fichero backend-explore.md en el d ``` -#### 📄 backend-explore.md +**📄 backend-explore.md** -Este comando realizará un análisis exhaustivo de tu sistema y lo dejará escrito dentro de la carpeta ```specs`` en un fichero llamado ``backend-explore.md``. Al final te mostrará un texto con el resumen del sistema y además escribirá el fichero de explore. +Este comando realizará un análisis exhaustivo de tu sistema y lo dejará escrito dentro de la carpeta ``specs`` en un fichero llamado ``backend-explore.md``. Al final te mostrará un texto con el resumen del sistema y además escribirá el fichero de explore. !!! tip "Sobre los permisos" Es posible que durante el análisis te pida permiso para hacer ciertas tareas. Le puedes ir dando permiso una a una o darle permiso en todo el workspace, eso lo dejamos a tu elección. @@ -250,7 +250,7 @@ Aquí dejamos claro: - **NO** se implementa código - **NO** se redefine el sistema -#### 📜 Prompt +**📜 Prompt** Para nuestro ejemplo, lo que haremos será escribir en el chat de ``Visual Studio Code`` el siguiente prompt: @@ -322,7 +322,7 @@ Además en el chat también hará un pequeño resumen de lo que ha propuesto com Veamos lo que contiene cada uno de esos ficheros. -#### 📄 proposal.md +**📄 proposal.md** Define la funcionalidad a alto nivel. @@ -335,7 +335,7 @@ Incluye: Responde a: ¿Qué se va a construir y por qué? -#### 📄 design.md +**📄 design.md** Describe el diseño técnico de la solución. @@ -349,7 +349,7 @@ Incluye: Responde a: ¿Cómo se va a construir y por qué se ha elegido este enfoque? -#### 📄 spec.md +**📄 spec.md** Define el comportamiento funcional esperado. @@ -362,7 +362,7 @@ Incluye: Responde a: ¿Qué debe hacer el sistema? -#### 📄 tasks.md +**📄 tasks.md** Descompone la implementación en tareas ejecutables. Quizá es el fichero más importante. @@ -391,7 +391,7 @@ El objetivo de esta fase es transformar los artefactos generados (proposal.md, d - Se siguen las decisiones técnicas definidas en design.md - Se ejecutan las tareas en el orden establecido en tasks.md -#### 📜 Prompt +**📜 Prompt** Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguiente prompt: @@ -426,6 +426,7 @@ Y llegamos a la última etapa que nos define open spec, el último paso es archi El objetivo de esta fase es marcar la funcionalidad como completada, consolidar todos los artefactos generados durante el proceso y dejar el sistema en un estado estable, coherente y preparado para nuevas evoluciones. En esta fase se asegura que: + - La funcionalidad ha sido correctamente implementada y validada - No existen incidencias críticas pendientes - La documentación asociada al cambio está completa y actualizada @@ -434,7 +435,7 @@ En esta fase se asegura que: Aunque parezca mentira, este paso es muy importante ya que nos servirá para actualizar el contexto del sistema y archivar todos los cambios para futuras consultas. -#### 📜 Prompt +**📜 Prompt** De nuevo nos vamos al chat de ``Visual Studio Code`` el siguiente prompt: @@ -469,7 +470,7 @@ Esto puede provocar: - Dificultad para futuras evoluciones - Desalineación entre código y documentación -#### 📜 Actualización +**📜 Actualización del contexto** Además, para forzar al ``modelo gratuito`` y dejarlo todo listo, es recomendable lanzar un último prompt que nos actualice el fichero de `backend-explore.md` @@ -479,3 +480,187 @@ Además, para forzar al ``modelo gratuito`` y dejarlo todo listo, es recomendabl Actualiza el fichero de backend-explore con los nuevos datos implementados ``` + + +## Generación de frontend + +Bueno, pues ahora que ya tenemos el backend implementado, realizaremos de nuevo un ciclo completo de Open Specs pero está vez para frontend: + +``` +1. Explore +2. Propose +3. Apply +4. Archive +``` + +### Explore + +De nuevo el objetivo de esta fase es analizar el sistema existente, sin modificar nada, pero esta vez nos centraremos en el frontend. + +**📜 Prompt** + +Vamos al chat de ``Visual Studio Code`` y escribimos el comando: + +``` +opsx:explore + +Analiza el proyecto actual que está en el directorio "frontend", es una aplicación Angular. Ojo no escanees la carpeta de "node_modules" no tiene sentido. Una vez analizado, responde: + +1. ¿Cómo están implementados los CRUD existentes? +- Componentes +- Servicios +- Modelos + +2. ¿Qué estructura siguen los dominios? + +3. ¿Cómo se implementan las operaciones? +- Listado +- Creación/edición +- Borrado +- Cómo funcionan las ventanas de creación y edición (modales) + +4. ¿Como se comunican frontend con backend? +- Servicios en Angular +- Construcción de URLs + +5. ¿Qué patrones o estructuras comunes se repiten en los CRUD existentes? +- Clases reutilizables +- Lógica repetida +- Estructuras comunes entre dominios + + +Analiza únicamente la parte de frontend (Angular) +NO propongas soluciones. +NO diseñes nuevas funcionalidades. +Solo analiza el sistema actual. +Escríbe el resultado del contexto dentro del fichero frontend-explore.md en el directorio de las specs para poder utilizarlo en siguientes fases. + +``` + +Este comando realizará un análisis exhaustivo de tu sistema y lo dejará escrito dentro de la carpeta ``specs`` en un fichero llamado ``frontend-explore.md``. Al final te mostrará un texto con el resumen del sistema y además escribirá el fichero de explore. Este análisis estará más centrado en el frontend y debes pedirle que compruebe como se comunica con el backend para que lo tenga en cuenta. + + +### Propose + +Una vez definido el análisis inicial, lo siguiente es pedirle una propuesta de lo que queremos construir. + +**📜 Prompt** + +De nuevo en el chat de ``Visual Studio Code`` escribimos el comando: + +``` +/opsx:propose manage-customers-frontend + +Define la funcionalidad de gestión de clientes basándote en el sistema actual y en los patrones identificados en la fase Explore, tienes el resultado en el fichero "frontend-explore.md". Además tendrás que ver el cambio realizado en la spec de "manage-customers-backend", sobre todo los endpoints generados que lo tienes definido en "FRONTEND_API_CONTRACT.md" + +Nos han pedido esta nueva funcionalidad. + +Por un lado necesita poder tener una base de datos de sus clientes. Para ello nos ha pedido que si podemos crearle una pantalla de CRUD sencilla, al igual que hicimos con las categorías donde él pueda dar de alta a sus clientes. + +Nos ha pasado un esquema muy sencillo de lo que quiere, tan solo quiere guardar un listado de los nombres de sus clientes para tenerlos fichados, y nos ha hecho un par de pantallas sencillas muy similares a Categorías. + +Un listado sin filtros de ningún tipo ni paginación. + +Un formulario de edición / alta, cuyo único dato editable sea el nombre. Además, la única restricción que nos ha pedido es que NO podamos dar de alta a un cliente con el mismo nombre que otro existente. Así que deberemos comprobar el nombre, antes de guardar el cliente. + +Para empezar te daré unos consejos: +- Recuerda crear la tabla de la BBDD y sus datos +- Intenta primero hacer el listado completo, en el orden que más te guste: frontend o backend. +- Completa el listado conectando ambas capas. +- Termina el caso de uso haciendo las funcionalidades de edición, nuevo y borrado. Presta atención a la validación a la hora de guardar un cliente, NO se puede guardar si el nombre ya existe. + + +Te voy a dar otras directrices que pienso que te pueden servir: +- Se necesita un CRUD de clientes +- Un cliente solo tiene: id, name +- El listado será simple, sin filtros ni paginación +- Existirá un formulario de alta / edición en modal +- El único campo editable será el nombre +- No se puede crear un cliente con un nombre ya existente, será una validación obligatoria que deberemos cumplir en el guardado + + +Necesito que definas: + +1. Descripción de la funcionalidad + +2. Reglas de negocio + +3. Diseño frontend: +- Componentes necesarios +- Flujos de interacción (listado, abrir modal, guardar borrar) + +4. Uso de endpoints para llamar a backend + +5. Decisiones técnicas: +- Qué patrones del sistema actual se reutilizan + + +NO implementes código. +NO analices de nuevo el proyecto. +Basa la propuesta en los patrones detectados en la fase Explore. +Haz la propuesta únicamente de frontend. +Olvídate de los test, en frontend no tenemos tests. +Añade el nuevo punto de menú en el header para que se pueda acceder. +No te inventes estilos, respeta los estilos de las pantallas (anchuras, alturas, colores, disposición de las tablas). +Utiliza los alert dialog de Angular Material para las alertas, no uses la opción alert() del navegador. + +Tendrás que escribir los ficheros de proposal, design, spec y tasks en la propuesta correspondiente. +``` + +Puntos a destacar de este prompt: + +- Además del contexto inicial le hemos pedido que busque el fichero de `FRONTEND_API_CONTRACT.md` que es el fichero que generamos junto con el backend y que tiene las reglas de los endpoints del backend. Esto lo tenemos que hacer así ya que el modelo es gratuito y tiene limitaciones, no puede analizar frontend y backend a la vez en el mismo contexto. +- También le hemos pedido que **NO** invente estilos y que se fije en las pantallas existentes, además de decirle que utilice componentes de Angular Material. A veces los modelos gratuitos tienden a inventar mucho, por falta de contexto. De nuevo cuanto más concretos y precisos seamos, mejor implementará. + + +### Apply + +Cuando estemos de acuerdo con la propuesta que nos ha hecho la IA y sobre todo con las tasks que propone realizar, lanzamos la fase de implementación. + +**📜 Prompt** + +Esto es tan fácil como escribir en el chat de ``Visual Studio Code`` el siguiente prompt: + +``` + +/opsx:apply + +``` + +El agente empezará a realizar un montón de tareas y pedirnos permisos. Es posible que algunas de esas tareas fallen y él mismo lo reintente de otra forma. El resultado debería ser el código generado e implementado dentro de la carpeta de ``frontend`` y un resumen de todas las tareas realizadas y checkeadas por la IA. + + +### Pruebas del frontend + +Ahora sí, prueba de 🔥 fuego 🔥. Es hora de levantar el sistema completo, ``frontend`` y ``backend``, navegar por la aplicación y comprobar que todo funciona. + +Si algo no encaja, es buen momento para conversarlo con la IA y que realice los cambios necesarios hasta conseguir que todo funcione perfectamente. + + +### Archive + +Una vez tengamos todo funcionando y perfectamente implementado, pasamos a la última etapa para archivar y sincronizar nuestro cambio. + +**📜 Prompt** + +De nuevo nos vamos al chat de ``Visual Studio Code`` el siguiente prompt: + +``` +/opsx:archive +``` + +En ese caso, el sistema solicita confirmación para sincronizar los requisitos antes de archivar el cambio. + + +**📜 Actualización del contexto** + + +Y por último forzamos una actualización del contexto inicial. + +``` +Actualiza el fichero de frontend-explore con los nuevos datos implementados +``` + + + + From 919dc8f2ef5319263a92dbc7e6c756b7eb8181dd Mon Sep 17 00:00:00 2001 From: lauramgll <156073035+lauramgll@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:31:14 +0200 Subject: [PATCH 07/13] Open Spec - Ejercicio clientes (modelo pago) --- docs/assets/images/specs-customer-paid_1.png | Bin 0 -> 27554 bytes docs/assets/images/specs-customer-paid_2.png | Bin 0 -> 24504 bytes docs/assets/images/specs-customer-paid_3.png | Bin 0 -> 20918 bytes docs/assets/images/specs-customer-paid_4.png | Bin 0 -> 7853 bytes docs/specs/customers_free.md | 46 +- docs/specs/customers_paid.md | 464 ++++++++++++++++++- 6 files changed, 495 insertions(+), 15 deletions(-) create mode 100644 docs/assets/images/specs-customer-paid_1.png create mode 100644 docs/assets/images/specs-customer-paid_2.png create mode 100644 docs/assets/images/specs-customer-paid_3.png create mode 100644 docs/assets/images/specs-customer-paid_4.png diff --git a/docs/assets/images/specs-customer-paid_1.png b/docs/assets/images/specs-customer-paid_1.png new file mode 100644 index 0000000000000000000000000000000000000000..e9def682517dfebbab199779fcb83ea3e9f3308a GIT binary patch literal 27554 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 literal 0 HcmV?d00001 diff --git a/docs/assets/images/specs-customer-paid_2.png b/docs/assets/images/specs-customer-paid_2.png new file mode 100644 index 0000000000000000000000000000000000000000..dfab0487f78106bfbc435303738b44afa773eea5 GIT binary patch literal 24504 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