Tabla de Contenidos
¡Esta es una revisión vieja del documento!
5. Automatizando el proyecto con IA
Objetivo del tema: que la calidad la vigilen tests que genera la IA en los cuatro niveles (arquitectura, unitarios, API y E2E) y que ningún issue llegue a develop sin pasar una revisión. Al acabar, cerrar un issue es /git-flow finalizar y todo lo demás ocurre solo, y un CRUD nuevo es una sola orden.
Tests de arquitectura con ArchUnit
Las reglas de arquitectura (las de Programación y DWS: capas, nombres, sin ciclos, domain sin Spring) las hace cumplir ArchUnit con tests que fallan cuando una clase está donde no debe. Lo nuevo es que los escribe la IA a partir de las reglas en castellano:
/crear-test-arquitectura - Las clases de controller solo dependen de domain. - Las de persistence solo dependen de domain. - Nada en domain importa org.springframework ni jakarta.persistence. - Los controladores acaban en Controller y están en ..controller.. - Los repositorios de persistence acaban en RepositoryImpl e implementan una interfaz de domain. - Sin ciclos entre paquetes.
Cada test lleva en su @DisplayName y en un comentario qué comprueba, por qué y las excepciones:
@Test
@DisplayName("domain no depende de Spring: el dominio tiene que compilar y probarse sin el framework")
void domainSinSpring() {
noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat().resideInAPackage("org.springframework..")
.check(clases);
}
No hay un documento aparte con la arquitectura. Mantener un Markdown sincronizado con los tests es un trabajo que nadie hace, así que la fuente de verdad son los tests con su descripción. Para cambiar una regla se le pide a la skill que cambie el test. Cuando se añade una regla que el código actual aún no cumple, FreezingArchRule.freeze(regla) congela las violaciones existentes y solo falla con las nuevas.
Tests unitarios con JUnit
Los tests unitarios prueban la lógica del dominio sin Spring y sin base de datos: un servicio con sus repositorios sustituidos por mocks de Mockito. Son los más rápidos y los que mejor localizan un error. /crear-test-unitarios los genera para una clase o un paquete del dominio:
/crear-test-unitarios PedidoService
Lo que hace: lee la clase, identifica cada comportamiento (lo que devuelve, lo que valida, lo que lanza) y escribe un test por comportamiento en backend/src/test/java, con el mismo paquete, @DisplayName en castellano que diga el caso y Mockito para las dependencias. Los ejecuta con mvn test y se para si alguno falla por un error del código, en vez de retocar el test para que pase.
Qué no prueban: controladores ni repositorios. Eso es de los tests de API.
Tests de API con Playwright
Los tests de API prueban cada endpoint de verdad: peticiones HTTP contra el backend del worktree, comprobando el código de estado y el JSON. Se escriben con Playwright, igual que los E2E (mismo lenguaje, misma configuración, misma carpeta): Playwright trae para esto el fixture request, que hace peticiones sin abrir ningún navegador.
No forman parte del build: mvn test no los ejecuta. Se lanzan aparte, cuando se quiere, con npm run api-test (tema 3), que resetea la base de datos del worktree, levanta la aplicación y ejecuta el proyecto api de Playwright. /crear-test-api los genera para una entidad:
/crear-test-api Pedido
Por cada endpoint del controlador, en frontend/tests/api/pedidos.spec.ts, un test del caso bueno y uno por cada error que el plan o las validaciones prevén: 400 con un campo obligatorio vacío, 404 con un id que no existe, 409 con un valor único repetido. Los datos de partida son los del R__ de la entidad; los tests que escriben se limpian a sí mismos (borran lo que crean) para que se puedan pasar dos veces seguidas.
test('POST /api/pedidos sin número devuelve 400 y el nombre del campo', async ({ request }) => {
const res = await request.post('/api/pedidos', { data: { fecha: '2026-10-14', clienteId: 1 } });
expect(res.status()).toBe(400);
expect((await res.json()).errores.numero).toBeDefined();
});
Los de API y los E2E son dos proyectos del mismo playwright.config.ts, separados por carpeta; el de API no necesita navegador:
export default defineConfig({
use: { baseURL: process.env.BASE_URL ?? 'http://localhost:4200' },
projects: [
{ name: 'api', testDir: 'tests/api' },
{ name: 'e2e', testDir: 'tests/e2e', use: { browserName: 'chromium' } },
],
});
mvn test corre unitarios y arquitectura en cada build; npm run api-test y npm run e2e-test corren API y E2E cuando se pide. Son los mismos comandos para el alumno, para el agente y para CI.
Tests E2E con Playwright
Los tests E2E prueban la aplicación completa desde el navegador, como lo haría el usuario. Los criterios de aceptación del plan son frases comprobables. /crear-test-e2e las convierte en tests de Playwright en TypeScript, frontend/tests/e2e/<entidad>.spec.ts, y los deja pasando:
/crear-test-e2e planes/12-alta-clientes.md
Cómo trabaja: lee los criterios, abre la aplicación del worktree con el Playwright MCP para ver qué hay en pantalla, escribe el test y lo ejecuta con npm run e2e-test; si falla por un selector o un texto, lo arregla y lo vuelve a pasar. La IA interviene al generar, no al ejecutar: lo que queda en el repositorio es un .spec.ts normal, determinista, que CI ejecuta sin ninguna IA.
Reglas que la skill tiene que respetar (están en convenciones-frontend):
- Selectores por
data-testidy por rol (getByRole,getByLabel), nunca por clase CSS. Un test que depende del CSS se rompe al primer cambio de estilo. - Un test por criterio de aceptación, con el criterio como nombre del test.
- Los tests cuentan con los datos de prueba del tema 3 y se pasan sobre la base de datos recién reseteada (
docker compose down -v && docker compose up -d).
Revisión por IA antes de cerrar
Sin pull request nadie mira el código antes de que llegue a develop. Lo mira la IA. /revisar compara el diff de la rama contra develop con dos cosas:
- El plan del issue: ¿hace lo que se pidió, y como se pidió? ¿Falta algún criterio de aceptación? ¿Ha hecho cosas que no estaban en el plan?
- Las skills de convenciones: ¿está en las capas correctas, con los nombres correctos, con migración y datos de prueba, con
data-testid?
Y clasifica cada cosa que encuentra en grave (no cumple el plan, rompe una convención, falta un test) o mejorable. /git-flow finalizar lanza /revisar como primer paso y no fusiona si hay algo grave: enseña la revisión y se para. El alumno corrige (o le pide al agente que corrija) y vuelve a finalizar.
develop. La revisión es una ayuda para leer, no una excusa para no leer.
Calidad del código: las reglas y quien las aplica
/revisar dice si el código hace lo que pedía el plan y si sigue las convenciones. No dice si el código es bueno: métodos que hacen tres cosas, clases que se conocen demasiado, comentarios que repiten el código, tests que copian los valores que prueban. Para eso hacen falta dos skills más: una de conocimiento con las reglas de calidad y una de tarea que las aplica a un trozo de código y cuenta lo que encuentra.
calidad-codigo: las reglas
Una skill de conocimiento, como las de convenciones, pero sobre cómo se escribe código bueno en general. Se escribe una vez y se va completando cuando una revisión saca algo que no estaba. SKILL.md lleva la máxima y el índice; cada grupo de reglas, en su fichero:
.claude/skills/calidad-codigo/ ├── SKILL.md <- la máxima, el orden de prioridades y la tabla de ficheros ├── metodos.md ├── clases.md ├── comentarios.md └── tests.md
La máxima: el código se escribe para que una persona lo entienda al leerlo y para que un agente lo pueda modificar sin equivocarse. Consecuencia que lo cambia todo: una repetición pequeña y visible es aceptable; una indirección para ahorrar dos líneas (un método que solo reenvía, un genérico) no lo es, porque el agente no olvida los dos sitios que hay que tocar y la persona sí pierde el hilo.
Cuando dos reglas chocan, gana la primera de esta lista: mantenibilidad, testabilidad, determinismo, separación de responsabilidades.
| Fichero | Reglas (las que entran; se amplía con lo que salga en las revisiones) |
|---|---|
metodos.md | El método público cuenta la historia entera y se lee de arriba abajo. Se trocea por cosa que hace, no por fase. Con pocos casos (hasta 4), un método lineal por caso en vez de un genérico. El nombre dice qué hace, no cómo. Cabe en una pantalla y tiene como mucho tres parámetros. El cálculo puro se separa de la entrada/salida (base de datos, HTTP) para poder probarlo sin montar nada |
clases.md | Una clase, una responsabilidad. Composición antes que herencia. Un DTO o record agrupa solo lo homogéneo y sus campos se nombran por lo que son, no por quién los usa. Nada estático con estado. La interfaz del Repository en domain dice lo que el dominio necesita, no lo que la base de datos ofrece |
comentarios.md | El código se explica solo: si hace falta un comentario para entender qué hace, el nombre está mal. El único comentario que se escribe es el porqué que leer el código no revela (una decisión, una restricción externa). Nada de comentarios que repiten la línea de debajo ni de código comentado |
tests.md | Un test por comportamiento, con el caso en el nombre. El test no copia los valores del código que prueba (si el IVA es una constante, el test la lee de producción y comprueba la relación, no escribe 21 otra vez). Sin lógica en el test: ni if ni bucles. Lo que se prepara se ve en el propio test |
Y en SKILL.md, cuatro olores de diseño que vienen antes del código y que el agente tiene que buscar aunque no haya regla concreta: una decisión que se toma en dos sitios; un método que vive en una clase que no es la dueña del concepto; un if que no cubre todos los casos; un retorno defensivo (return null, lista vacía) que tapa un error en vez de fallar.
La description de la skill dice cuándo cargarla: «Reglas de calidad del código del proyecto. Cargar al escribir o revisar cualquier clase Java o TypeScript, y siempre desde /revisar-calidad».
/revisar-calidad: quien las aplica
Una skill de tarea que recibe una ruta (una clase, un paquete, backend/src/main/java entero) y devuelve un informe. No toca el código: el alumno decide qué corregir y lo pide después.
/revisar-calidad backend/src/main/java/com/tienda/pedidos
Lo que hace, en orden:
- Carga
calidad-codigoy las skills de convenciones. - Lee el código de la ruta. Solo lee:
allowed-tools: Read, Grep, Glob. SinBashy sinEdit. - Por cada cosa que encuentra, un hallazgo con: la regla incumplida (fichero y sección de
calidad-codigo), el sitio (fichero:línea) y el coste del código tal como está (qué se duplica, qué no se puede probar, qué se rompe al cambiarlo). Sin coste no hay hallazgo: eso separa un defecto de una preferencia. - Antes de proponer cambiar la forma de algo sin cambiar lo que hace (extraer, mover, renombrar), mira cómo lo resuelven tres clases hermanas (otros controladores, otros servicios). Si lo hacen igual, es una convención del proyecto y no se reporta. Si se aparta, se reporta citando una hermana como referencia.
- Clasifica en grave (rompe una regla con coste claro, o un olor de diseño) y mejorable, y ordena de más a menos grave.
Un hallazgo bien hecho y dos mal hechos:
GRAVE PedidoService.java:48 metodos.md «cálculo puro y efectos secundarios»
calcularTotal() lee los pedidos de la base de datos y los suma en el mismo método.
Coste: no se puede probar la suma sin base de datos; el test unitario actual la monta entera.
MAL "extraer las líneas 20-24 a un método privado para mejorar la legibilidad"
(no mira las clases hermanas: todos los servicios lo escriben así; y no dice qué cuesta)
MAL "el método es demasiado largo"
(sin regla ni coste: es una opinión)
Después del informe, el alumno lee, decide y pide: «corrige los graves» o «el segundo no, es así a propósito». Ahí sí se edita el código, en la conversación normal, y se vuelve a pasar /revisar-calidad para confirmar.
Cuándo se pasa: después de cada /crud (es donde más código sale de golpe), sobre el paquete de un issue antes de finalizar si ha sido grande, y sobre todo el backend antes de cada publicar. Lo que /revisar-calidad saca una y otra vez es una regla que falta en las convenciones: se añade y deja de salir.
Los CRUD con cc-sdd
Los CRUD de este proyecto son siempre iguales: solo cambian la entidad y sus campos. Para que salgan iguales de verdad, con las mismas capas, los mismos nombres y los mismos tests, no basta con pedirlos bien: el agente tiene que tener la arquitectura escrita y leerla antes de diseñar nada. Eso es lo que hace el steering de cc-sdd, y por eso los CRUD se hacen con él.
Cómo se instala cc-sdd y cómo funcionan sus cinco fases está en el curso de SDD: Kiro: comandos SDD. Aquí solo lo que es de este proyecto: que tiene dos arquitecturas, la del cliente y la del servidor, y cómo se le cuentan.
Cuándo se genera la memoria del proyecto
/kiro-steering describe lo que ve en el código. Por eso se lanza después del primer CRUD del tema 4, ya revisado, y no antes: con el esqueleto vacío describiría nada, y con un CRUD mal hecho convertiría el error en norma.
Se instala y se lanza una vez, en la raíz del repositorio:
npx cc-sdd@latest --claude-skills --lang es (OpenCode: --opencode-skills) /kiro-steering
Los tres ficheros que genera, product.md, tech.md y structure.md, entran en el contexto siempre, en todas las fases de todas las specs. Así que se revisan y se dejan cortos, con solo lo común: qué es el proyecto, que es Spring Boot más Angular más MariaDB, el reparto backend/ y frontend/, los comandos de arrancar y probar y las dos ramas.
Dos arquitecturas, dos ficheros de steering
Lo específico de cada parte no va en esos tres, sino en dos ficheros de steering condicionales, que solo entran en el contexto cuando la spec toca esa carpeta. Se crean con /kiro-steering-custom y llevan en la cabecera el patrón de ficheros que los activa:
--- inclusion: fileMatch fileMatchPattern: "backend/**" --- # Arquitectura del servidor ...
Y frontend.md igual, con frontend/. Concretar las dos arquitecturas es trabajo del alumno: /kiro-steering-custom pone la cabecera y un borrador; el contenido se escribe mirando el CRUD de referencia, y tiene que responder a esto:
^ backend.md ^ frontend.md ^
| Las capas controller / domain / persistence y qué va en cada paquete | Carpeta por entidad y qué componentes tiene (listado y formulario) |
| Nombres: XxxController, XxxService, Xxx (dominio), XxxRepository en domain y XxxRepositoryImpl en persistence, XxxJpaEntity, XxxRequest / XxxResponse, el mapper de MapStruct | Nombres de ficheros, de componentes y de rutas (/clientes, /clientes/nuevo, /clientes/:id) |
| Dónde se valida (Bean Validation en el Request) y cómo se devuelven los errores: ApiExceptionHandler, códigos 400 / 404 / 409 y el formato del JSON de error | El servicio HTTP de cada entidad y cómo se llama a la API |
| Migraciones V<n>__ y datos de prueba R__, con un ejemplo | Formularios reactivos y cómo se muestran las validaciones |
| Cómo se prueba: mvn test y qué prueban los unitarios | data-testid en todo lo que un test toca; cómo se prueba: npm run api-test y npm run e2e-test |
Son exactamente las reglas de convenciones-backend y convenciones-frontend del tema 4. Para no mantener el mismo texto en dos sitios, la fuente pasa a ser el steering: las dos skills de convenciones se quedan en una línea, «lee .kiro/steering/backend.md», y así el flujo de plan de los issues normales y las specs de cc-sdd leen lo mismo.
==== Un CRUD de principio a fin ====
<code>
/kiro-spec-init CRUD de pedidos: numero (texto, único, obligatorio), fecha (obligatoria),
cliente (relación con Cliente, obligatoria), total (decimal, >= 0)
/kiro-spec-requirements
/kiro-spec-design -y
/kiro-spec-tasks -y
/kiro-impl
</code>
* En requirements se revisa que estén los casos de error (campo vacío, NIF repetido, id inexistente): son los que luego prueban los tests.
* En design se comprueba una sola cosa: que es igual que el CRUD de referencia. Si no lo es, lo que falla no es el diseño sino el steering: se añade la línea que faltaba en backend.md o frontend.md y se repite la fase. Así cada CRUD deja el steering un poco mejor para el siguiente.
* Los tests se generan igual que en cualquier issue, pero a partir de la spec: /crear-test-api .kiro/specs/crud-pedidos/requirements.md y lo mismo con /crear-test-e2e. Y /revisar compara el diff con esa spec en lugar de con un plan.
La lección: lo que el steering no dice, el agente lo inventa distinto cada vez. Si el segundo CRUD sale distinto del primero, falta una línea en el steering, no un prompt mejor.
===== Ejercicios =====
==== Ejercicio 1 ====
* Crea un issue «Tests de arquitectura» e inícialo.
* Escribe la skill /crear-test-arquitectura: recibe reglas en castellano y genera los tests ArchUnit en backend/src/test, cada uno con @DisplayName y comentario con qué comprueba, por qué y las excepciones.
* Lánzala con las seis reglas del tema. mvn test tiene que pasar.
* Mueve a mano el servicio del CRUD al paquete controller y comprueba que mvn test falla con el mensaje del test. Deshazlo.
* Finaliza el issue.
==== Ejercicio 2 ====
* Crea un issue «Tests unitarios y de API del CRUD» e inícialo.
* Instala Playwright en frontend/ (npm init playwright@latest) con los proyectos api y e2e y los scripts api-test y e2e-test del tema 3.
* Escribe la skill /crear-test-unitarios: recibe una clase o un paquete del dominio y genera un test JUnit 5 por comportamiento, con Mockito y sin Spring.
* Lánzala con el servicio del CRUD del tema 4. mvn test tiene que pasar.
* Escribe la skill /crear-test-api: recibe una entidad y genera tests/api/<entidad>.spec.ts con un test por endpoint del caso bueno y uno por cada error previsto, y lo ejecuta con npm run api-test hasta que pase.
* Lánzala con la entidad del CRUD. mvn test no debe ejecutarlos; npm run api-test tiene que pasar dos veces seguidas.
* Introduce un error a mano en una validación del servicio, reconstruye el backend y comprueba qué tests lo detectan: tiene que caer al menos un unitario y uno de API. Deshazlo.
* Finaliza el issue.
==== Ejercicio 3 ====
* Crea un issue «Tests E2E del CRUD» e inícialo.
* Escribe la skill /crear-test-e2e: recibe un fichero de plan, resetea la base de datos, abre la aplicación con el MCP de Playwright, escribe un .spec.ts por criterio de aceptación y lo ejecuta hasta que pase.
* Lánzala con el plan del CRUD del tema 4. npm run e2e-test tiene que pasar dos veces seguidas.
* Finaliza el issue.
==== Ejercicio 4 ====
* Crea un issue «Revisión por IA» e inícialo.
* Escribe la skill /revisar: diff de la rama contra develop, comparación con el plan del issue y con las skills de convenciones, salida clasificada en grave / mejorable.
* Amplía /git-flow finalizar para que lance /revisar primero y se pare si hay algo grave.
* Finaliza el issue.
==== Ejercicio 5 ====
* Crea un issue «Revisión de calidad» e inícialo.
* Escribe la skill de conocimiento calidad-codigo con los cuatro ficheros de la tabla y los olores de diseño en SKILL.md. Pide al agente un primer borrador a partir de la tabla y corrígelo: cada regla con una frase de ejemplo de código que la cumple y otra que no.
* Escribe la skill /revisar-calidad con los cinco pasos, allowed-tools solo de lectura y el formato de hallazgo del ejemplo.
* Pásala sobre el paquete del CRUD del tema 4. Lee el informe: para cada hallazgo, decide si es un defecto o una preferencia y por qué.
* Pide que corrija solo los graves y vuelve a pasarla: no deben salir.
* Finaliza el issue.
==== Ejercicio 6 ====
* Crea un issue «cc-sdd y steering» e inícialo.
* Instala cc-sdd en la raíz y lanza /kiro-steering. Lee los tres ficheros y déjalos cortos, con solo lo común.
* Con /kiro-steering-custom crea backend.md y frontend.md condicionales y rellénalos con la tabla de arriba, mirando el CRUD del tema 4. Deja las dos skills de convenciones apuntando a ellos.
* Finaliza el issue.
==== Ejercicio 7 ====
* Crea un issue para la segunda entidad de tu proyecto e inícialo.
* Haz el CRUD con las cinco fases de cc-sdd. En la fase de diseño, compáralo con el CRUD del tema 4: cada diferencia es una línea que falta en el steering. Corrígela y repite la fase hasta que salga igual.
* Genera los tests con /crear-test-api y /crear-test-e2e a partir de requirements.md y comprueba que mvn test, npm run api-test y npm run e2e-test pasan.
* Quita a mano de requirements.md un caso de error sin quitar su código y lanza /git-flow finalizar. /revisar'' tiene que marcarlo como grave y la fusión no debe hacerse. Restaura el fichero y finaliza.
