Durante diez módulos has tenido un ejemplo guiado: Nómada Tareas se construía delante de ti, lección a lección, y cada concepto nuevo llegaba con su trozo de código ya pensado. Eso se acaba aquí. En este módulo construyes tu propio producto, solo, con el mismo método y sin que nadie te dé el código. Nómada Tareas no desaparece: pasa a ser la referencia —seis tareas, 48 horas, reglas R1–R10, 124 pruebas, 58,3 kB en tres peticiones, LCP de 1,9 s— contra la que compararás lo que hagas. Y esta primera lección no escribe ni una línea de lógica de negocio a propósito, porque el error más caro de un proyecto propio no es un undefined inesperado: es empezar a programar sin saber qué se está construyendo. Aquí aprenderás a convertir una idea en un plan defendible: elegir el producto, recortarlo hasta un MVP honesto y escribir en negro sobre blanco lo que no vas a hacer; redactar historias de usuario con criterios de aceptación y priorizarlas con MoSCoW; diseñar el modelo de datos y las reglas de negocio —las nuevas R11 a R15— antes de tocar el editor; decidir la arquitectura por capas y las fronteras que no se cruzan; montar el repositorio, la cadena de herramientas y el flujo de Git; planificar en hitos con un Gantt y una estimación que no te mientas a ti mismo; y fijar desde el minuto cero la Definición de Hecho y el presupuesto de rendimiento y accesibilidad. Al terminar tendrás tres entregables reales: el documento de alcance, el modelo de datos con sus reglas, y un repositorio inicializado y verde.

Contenido

  1. Qué cambia a partir de ahora
  2. El producto por defecto: Órbita
  3. Tres alternativas de dominio con el mismo método
  4. Delimitar el alcance: el MVP
  5. La lista de lo que NO se hará
  6. Historias de usuario con criterios de aceptación
  7. Priorizar con MoSCoW
  8. El modelo de datos, escrito antes de programar
  9. Las reglas de negocio nuevas: R11 a R15
  10. La arquitectura por capas y las fronteras que no se cruzan
  11. El entorno: repositorio y cadena de herramientas
  12. La estructura de carpetas de partida
  13. Git en serio: ramas, commits y revisiones
  14. Hitos y estimación honesta
  15. La Definición de Hecho
  16. El presupuesto de rendimiento y accesibilidad
  17. La plantilla de README.md
  18. Errores Comunes y Consejos
  19. Ejercicios
  20. Conclusión

  1. Qué cambia a partir de ahora

Hasta la lección 10-06, el curso funcionaba así: se explicaba un concepto, se aplicaba a Nómada Tareas, y el código aparecía. Tú lo leías, lo entendías, lo adaptabas en los ejercicios. Es la forma correcta de aprender los fundamentos, y es exactamente lo que había que hacer.

Pero hay una diferencia enorme entre entender código ajeno y producir código propio desde cero, y no se salva leyendo más. Se salva construyendo. Este módulo es esa construcción.

A partir de aquí cambian tres cosas:

Antes (M1–M10) Ahora (M11)
El código se te daba resuelto El código lo escribes tú; aquí recibes método, plantillas y criterios
Los ejercicios eran de práctica Los ejercicios son entregables del proyecto
Las soluciones eran el código correcto Las soluciones son criterios de aceptación y rúbricas
El proyecto era Nómada Tareas El proyecto es tuyo; Nómada Tareas es la referencia
El objetivo era aprender un concepto El objetivo es terminar un producto

Ese último punto merece énfasis. La habilidad que separa a quien sabe JavaScript de quien es desarrollador no es conocer más métodos de array: es terminar cosas. Terminar significa que funciona, que está probado, que se puede desplegar, que otra persona lo entiende y que tú puedes defender por qué está hecho así. Ese es el listón de este módulo.

Y hay una advertencia honesta que conviene leer despacio. La parte difícil de un proyecto propio no es técnica. Es que nadie te dice cuándo has terminado, nadie corrige tu alcance cuando crece, y nadie te avisa de que llevas tres días puliendo una animación mientras la funcionalidad principal sigue a medias. El método de esta lección existe precisamente para poner esas barandillas antes de que las necesites.

  1. El producto por defecto: Órbita

El producto por defecto de este módulo se llama Órbita. Es una aplicación de gestión de trabajo para equipos pequeños: la misma familia que Nómada Tareas, pero claramente más ambiciosa, con seis extensiones que en el curso nunca se implementaron y que tendrás que resolver por tu cuenta.

Nota sobre los datos. Órbita, sus usuarios de ejemplo y cualquier organización que aparezca en tus datos de prueba deben ser ficticios. No uses nombres, correos ni datos reales de compañeros, clientes o familiares, ni siquiera «para probar»: en cuanto eso llega a un repositorio público o a un despliegue, deja de ser una prueba y pasa a ser un tratamiento de datos personales. En la lección 11-03 y en la 11-05 volveremos sobre este punto con detalle.

Las seis extensiones sobre Nómada Tareas son estas:

# Extensión Qué añade sobre Nómada Tareas Concepto del curso que reutiliza
E1 Usuarios y asignación Las personas dejan de ser un string y pasan a ser entidades con id, rol y permisos; responsable y revisor son referencias 04-01, 05-02, 05-03
E2 Subtareas Una tarea puede descomponerse en un árbol de hasta tres niveles, con horas y progreso agregados 03-07 (recursividad)
E3 Etiquetas con filtro múltiple Filtrar por varias etiquetas a la vez, con modo Y / O, combinable con los demás filtros 03-06, 04-05
E4 Vista de calendario Una rejilla mensual que sitúa las tareas por fechaLimite, con navegación entre meses 04-04, 06-05, Intl de 07-06
E5 Informe de carga exportable Carga por persona y semana, exportable a CSV y JSON descargable desde el navegador 04-05 (reduce), 07-06 (Blob)
E6 Historial de cambios Registro inmutable de quién cambió qué y cuándo, con vista de auditoría por tarea 04-08, 05-03

Ninguna de las seis es decorativa. Cada una obliga a resolver un problema real que Nómada Tareas esquivaba:

  • E1 rompe la simplificación de responsable: 'Iván'. En cuanto hay entidades referenciadas, aparecen la integridad referencial (¿qué pasa con las tareas de un usuario que se borra?) y los permisos.
  • E2 convierte una lista plana en un árbol. Todo lo que era array.filter(...) pasa a necesitar recorrido recursivo, y las horas totales dejan de ser una suma directa.
  • E3 parece trivial y no lo es: combinar N filtros con dos semánticas distintas y mantenerlos en la URL (06-04, enrutador) exige pensar el estado.
  • E4 es la primera vista que no es una lista, y por tanto la primera que no puedes resolver copiando TableroVista.
  • E5 es la primera funcionalidad que produce un fichero, con todo lo que eso implica: formato, codificación, nombres, y el hecho de que un CSV mal escapado rompe en cuanto un título lleva una coma.
  • E6 introduce datos inmutables y crecientes, que es exactamente el tipo de dato que hace estallar localStorage (lección 11-03).

Órbita en una frase, que es como deberías poder describir cualquier producto:

Órbita es una aplicación web que permite a un equipo pequeño planificar su trabajo en tareas y subtareas, ver quién está sobrecargado y cuándo vencen las cosas, y saber en todo momento quién cambió qué.

Si no puedes escribir esa frase para tu producto, todavía no sabes qué estás construyendo.

  1. Tres alternativas de dominio con el mismo método

Si el dominio de las tareas te aburre —es una razón perfectamente legítima; vas a pasar semanas con esto—, elige otro. El método es idéntico y el módulo entero funciona igual. Estas son las tres alternativas propuestas, cada una con el mapeo de las seis extensiones:

Extensión Órbita (tareas) Aforo (reservas de sala) Racha (hábitos) Estantería (inventario)
E1 · Usuarios Responsable y revisor Quién reserva y quién autoriza Persona y grupo de apoyo Responsable de almacén
E2 · Árbol Subtareas Sala → subespacios (mesa, cabina) Hábito → pasos diarios Categoría → subcategoría → artículo
E3 · Filtro múltiple Etiquetas Equipamiento (proyector, pizarra) Ámbitos (salud, estudio) Etiquetas de producto
E4 · Calendario Fecha límite La vista central: ocupación por franjas Rejilla de constancia mensual Caducidades y reposiciones
E5 · Informe Carga por persona Ocupación por sala y semana Racha y porcentaje de cumplimiento Valoración de existencias
E6 · Historial Quién cambió qué Cambios y cancelaciones de reserva Registro diario (ya es historial) Movimientos de entrada y salida
Regla dura característica No cerrar con subtareas abiertas No solapar dos reservas en la misma sala Un registro por hábito y día El stock nunca puede ser negativo

Fíjate en la última fila: cada dominio tiene una regla dura característica que es la que da personalidad al modelo y la que más pruebas va a necesitar. En Aforo es el solapamiento de intervalos, que es un problema clásico y bastante más sutil de lo que parece. En Racha es la unicidad por día, que obliga a pensar zonas horarias. En Estantería es una invariante numérica que hay que defender en todas las operaciones.

Criterio para elegir, en orden de importancia:

  1. Que te importe. Vas a dedicarle muchas horas y nadie te va a obligar. La motivación es un recurso de proyecto tan real como el tiempo.
  2. Que puedas enseñárselo a alguien. Si en una entrevista puedes explicar el dominio en treinta segundos, sirve.
  3. Que tenga al menos una regla dura no trivial. Un CRUD sin reglas no demuestra nada; es el proyecto que hace todo el mundo.
  4. Que quepa. Si tu idea es «como Jira pero mejor», no cabe. Recórtala hasta que quepa, que es justo el apartado siguiente.

En el resto del módulo usaré Órbita en todos los ejemplos, pero cada plantilla es directamente reutilizable con cualquiera de los otros tres dominios. Cuando veas una tabla o una plantilla, sustituye los nombres y sigue.

  1. Delimitar el alcance: el MVP

MVP son las siglas de Minimum Viable Product: producto mínimo viable. Las dos palabras importan y casi siempre se entiende mal una de las dos.

  • Mínimo no significa «cutre». Significa que no hay nada que puedas quitar sin que deje de resolver el problema.
  • Viable no significa «una demo». Significa que alguien podría usarlo de verdad para lo que promete.

Un MVP de Órbita que no permitiera marcar una tarea como hecha sería mínimo pero no viable. Uno que tuviera temas de color, atajos de teclado personalizables y notificaciones por correo sería viable pero no mínimo.

La prueba que uso para decidir si algo entra en el MVP es una sola pregunta:

Si esto no estuviera, ¿el producto seguiría sirviendo para lo que dice su frase?

Si la respuesta es «sí, incómodo pero sirve», no es MVP. Va a la versión 1.1.

Aplicado a Órbita, este es el corte:

Funcionalidad ¿MVP? Razón
Crear, editar y borrar tareas Sin esto no hay producto
Cambiar de estado con las transiciones válidas (R6) Es el flujo de trabajo entero
Asignar responsable (E1) «Quién hace qué» está en la frase del producto
Subtareas de un nivel (E2 parcial) La descomposición es el diferencial; tres niveles pueden esperar
Filtro por responsable, estado y etiquetas (E3) Sin filtro, con 60 tareas el tablero es inútil
Persistencia local Un gestor que pierde los datos al recargar no es viable
Historial de cambios (E6) Es una promesa explícita de la frase del producto
Informe de carga (E5) Sí, mínimo La tabla en pantalla sí; la exportación a CSV, no
Vista de calendario (E4) No Útil, pero la lista con orden por fecha cubre la necesidad
Exportar a CSV/JSON No Comodidad, no necesidad
Tres niveles de subtareas No Un nivel demuestra el árbol; tres solo añaden casos límite
Sincronización con API No El MVP es de un solo dispositivo; la 11-03 lo amplía
Tiempo real multiusuario No Coste altísimo, valor bajo en un equipo de tres personas
Roles y permisos completos No El MVP asume que todos los usuarios son de confianza
Modo oscuro, temas, animaciones No Cosmético

De 15 candidatos, 8 entran. Ese ratio (aproximadamente la mitad) es normal y saludable. Si tu MVP contiene el 90 % de lo que se te ha ocurrido, no has recortado: has hecho una lista de deseos.

Una regla práctica de tamaño, contrastada: un MVP de proyecto personal debería ser algo que puedas construir en 6 a 10 semanas dedicándole entre 6 y 10 horas semanales. Si tu estimación honesta (apartado 14) da más, recorta ahora, porque tu estimación es optimista, no pesimista.

  1. La lista de lo que NO se hará

Esta es la sección del documento de alcance que más gente se salta y la que más proyectos salva. Un MVP definido solo por lo que incluye es ambiguo: todo lo que no está mencionado queda en una zona gris donde, un martes por la tarde, te parecerá razonable añadir «solo una cosita».

Escribir explícitamente lo que no se hará convierte cada añadido posterior en una decisión consciente en lugar de una deriva.

La plantilla tiene tres columnas y se escribe en docs/alcance.md:

## Fuera de alcance (v1.0)

| Qué no se hará | Por qué | Cuándo se reconsidera |
|---|---|---|
| Vista de calendario | La lista ordenada por fecha cubre la necesidad principal | v1.1, tras el hito H5 |
| Exportación a CSV/JSON | Comodidad; ningún criterio de aceptación depende de ella | v1.1 |
| Subtareas de más de un nivel | Un nivel ya demuestra el árbol; más niveles solo añaden casos límite | v1.2, si un usuario real lo pide |
| Sincronización con servidor | Requiere backend; el MVP es de un dispositivo | v2.0 (lección 11-03 prepara la frontera) |
| Tiempo real / colaboración | Coste desproporcionado para tres usuarios | No previsto |
| Autenticación de usuarios | No hay servidor; una autenticación solo en cliente es teatro de seguridad | v2.0, junto al backend |
| Notificaciones por correo | Requiere servidor y un proveedor de correo | No previsto |
| Internacionalización a varios idiomas | Un solo idioma; los formatos sí usarán `Intl` | v1.2 |
| Aplicación móvil nativa | La PWA cubre el caso de uso móvil | No previsto |
| Temas y personalización visual | Cosmético; no afecta a ningún criterio de aceptación | No previsto |

Tres detalles de la plantilla que no son adorno:

  • La columna «Por qué» te protege de ti mismo. Dentro de tres semanas no recordarás el razonamiento, solo la tentación.
  • «Cuándo se reconsidera» evita el «nunca» rencoroso. Aplazar no es rechazar, y decirlo así hace mucho más fácil aceptar el recorte.
  • «No previsto» es una respuesta legítima y conviene usarla sin culpa. No todo aplazado tiene que volver.

Una nota específica sobre la línea de autenticación, porque es un error clásico en proyectos de portafolio: una pantalla de login sin servidor no protege nada. Si los datos están en localStorage, cualquiera con las herramientas de desarrollo los ve. Poner un formulario de contraseña delante es peor que no ponerlo, porque comunica una seguridad que no existe. Si tu proyecto necesita autenticación de verdad, necesita servidor, y eso es la versión 2.0 (lección 11-07).

  1. Historias de usuario con criterios de aceptación

Una historia de usuario describe una necesidad desde el punto de vista de quien la tiene, no desde el de quien la implementa. Su valor no es el formato bonito: es que te obliga a decir para quién y para qué, y esas dos palabras deciden a menudo el diseño técnico.

La plantilla clásica:

**Como** <rol>
**quiero** <acción>
**para** <beneficio>

Y lo que de verdad hace útil una historia son sus criterios de aceptación: condiciones verificables que permiten decir «hecho» sin discusión. Se escriben en formato Dado / Cuando / Entonces (Given / When / Then), que tiene una ventaja enorme y nada casual: se traduce casi literalmente a una prueba de las que aprendiste en 08-03 y 08-06.

Aquí tienes tres historias reales de Órbita, completas.

6.1 Historia H-04: descomponer una tarea

### H-04 · Descomponer una tarea en subtareas

**Como** coordinadora del equipo
**quiero** dividir una tarea grande en subtareas
**para** repartir el trabajo entre varias personas y ver el avance parcial

**Criterios de aceptación**

1. **Dado** una tarea existente en estado `pendiente` o `en-curso`,
   **cuando** añado una subtarea con título y horas,
   **entonces** la subtarea aparece anidada bajo su tarea madre
   **y** las horas de la madre pasan a ser la suma de las de sus subtareas.

2. **Dado** una tarea con dos subtareas, una `hecha` y otra `pendiente`,
   **cuando** intento marcar la madre como `hecha`,
   **entonces** la aplicación lo impide con un mensaje que nombra
   la subtarea abierta (regla R13).

3. **Dado** una tarea con subtareas,
   **cuando** consulto su progreso,
   **entonces** veo el porcentaje de subtareas terminadas
   **y** ese porcentaje se anuncia con `aria-live` al cambiar.

4. **Dado** una subtarea,
   **cuando** intento convertirla en madre de su propia tarea madre,
   **entonces** la operación se rechaza con `ErrorDeRegla` (sin ciclos, R12).

**Notas técnicas**: el recorrido del árbol es recursivo (03-07).
La profundidad máxima es 3; el MVP solo expone 1 nivel.

Fíjate en el criterio 2. No dice «no se puede»: dice qué pasa exactamente, con qué mensaje y citando la regla. Esa precisión es la que convierte el criterio en una prueba de Jest de cinco líneas.

Y fíjate en el criterio 3: la accesibilidad está dentro del criterio de aceptación, no en una lista aparte que se revisa al final. Es la única forma de que no se olvide. Volveré sobre esto en el apartado 16 y en la lección 11-02.

6.2 Historia H-07: filtrar por varias etiquetas

### H-07 · Filtrar por varias etiquetas a la vez

**Como** miembro del equipo
**quiero** filtrar el tablero por varias etiquetas combinadas
**para** encontrar rápidamente el trabajo de un ámbito concreto

**Criterios de aceptación**

1. **Dado** un tablero con tareas etiquetadas,
   **cuando** selecciono dos etiquetas en modo *cualquiera* (O),
   **entonces** veo las tareas que llevan al menos una de las dos.

2. **Dado** la misma selección en modo *todas* (Y),
   **entonces** veo solo las tareas que llevan las dos.

3. **Dado** un filtro activo,
   **cuando** copio la URL y la abro en otra pestaña,
   **entonces** el filtro se aplica igual (el estado vive en la URL).

4. **Dado** un filtro que no devuelve ninguna tarea,
   **entonces** veo un estado vacío explicativo con un botón
   «Quitar filtros», no una lista en blanco.

5. **Dado** cualquier filtro,
   **cuando** cambia el número de resultados,
   **entonces** se anuncia por `aria-live` («12 tareas visibles de 47»).

El criterio 3 es el que tiene consecuencias arquitectónicas: obliga a que el filtro forme parte del estado de la aplicación y del enrutador (06-04 y el enrutador.js de Nómada Tareas), no a que sea una variable local de la vista. Un criterio de aceptación bien escrito decide diseño.

El criterio 4 es el que más se olvida en proyectos de portafolio: los estados vacíos. Una aplicación que muestra una zona en blanco cuando no hay resultados parece rota aunque funcione perfectamente.

6.3 Historia H-11: ver quién está sobrecargado

### H-11 · Ver la carga de trabajo del equipo

**Como** coordinadora
**quiero** ver cuántas horas abiertas tiene cada persona por semana
**para** repartir el trabajo antes de que alguien se sature

**Criterios de aceptación**

1. **Dado** un tablero con tareas asignadas y con fecha límite,
   **cuando** abro el informe de carga,
   **entonces** veo una tabla de personas por semanas ISO
   con las horas abiertas de cada celda.

2. **Dado** una persona con más de 40 horas abiertas en una semana,
   **entonces** su celda se destaca visualmente **y** con texto
   (no solo con color), y el informe indica cuántas personas están
   por encima del límite (R7 + R15).

3. **Dado** una tarea sin `fechaLimite`,
   **entonces** sus horas aparecen en una columna «Sin fecha»
   y nunca se reparten arbitrariamente en una semana.

4. **Dado** una tarea con subtareas,
   **entonces** sus horas se cuentan una sola vez (las de las hojas),
   nunca duplicadas entre madre e hijas (R12).

El criterio 4 es el que te va a costar una tarde entera si no lo escribes ahora. La doble contabilización de horas en árboles es uno de esos fallos que no rompe nada visiblemente: solo hace que todos los números estén mal.

Cuántas historias escribir. Para un MVP como este, entre 12 y 18 historias es el orden de magnitud correcto. Menos de 10 suele significar que las historias son demasiado gruesas («como usuario quiero gestionar tareas» no es una historia, es un módulo). Más de 25 suele significar que estás describiendo la implementación en lugar de la necesidad.

  1. Priorizar con MoSCoW

Tener historias no basta: hay que saber en qué orden. MoSCoW es un método de priorización simple y sorprendentemente eficaz, cuyo nombre viene de las iniciales de sus cuatro categorías:

Categoría Significado Regla de uso
M · Must have Sin esto no hay producto. Si falta, no se entrega Como máximo el 60 % del esfuerzo total
S · Should have Importante, pero el producto funciona sin ello ~20 % del esfuerzo
C · Could have Deseable si sobra tiempo ~20 % del esfuerzo
W · Won't have Explícitamente fuera de esta versión Es la lista del apartado 5

La regla del 60 % es la parte importante y la que casi todo el mundo incumple. Si tus Must consumen el 100 % del tiempo disponible, no tienes plan: tienes una apuesta. Cualquier imprevisto —y siempre hay imprevistos— te deja sin entregar. El 40 % restante en Should y Could es tu margen: cuando el tiempo aprieta, se sacrifican sin dolor y el producto sigue siendo entregable.

Priorización completa del MVP de Órbita:

ID Historia MoSCoW Est. (h) Hito Depende de
H-01 Ver el tablero de tareas M 6 H2
H-02 Crear una tarea con validación M 8 H2 H-01
H-03 Cambiar el estado con transiciones válidas M 5 H2 H-01
H-04 Descomponer en subtareas M 10 H3 H-02
H-05 Asignar responsable M 4 H3 H-02
H-06 Editar y borrar una tarea M 6 H3 H-02
H-07 Filtrar por varias etiquetas M 7 H4 H-01
H-08 Filtrar por responsable y estado M 3 H4 H-07
H-09 Persistencia local con migraciones M 8 H4 H-02
H-10 Historial de cambios por tarea S 9 H5 H-06
H-11 Informe de carga por persona y semana S 8 H5 H-05
H-12 Estado vacío y estados de error S 4 H5 H-01
H-13 Recorrido completo por teclado S 5 H5 H-01
H-14 Exportar el informe a CSV C 4 H6 H-11
H-15 Vista de calendario mensual C 12 H6 H-01
H-16 Instalable como PWA sin conexión C 6 H6 H-09

Reparto: Must 57 h (57 %), Should 26 h (26 %), Could 22 h (22 %). Total 105 h. La regla del 60 % se cumple con holgura.

Dos advertencias sobre esta tabla:

  • La columna «Depende de» es la que define el orden real. MoSCoW dice qué es importante; las dependencias dicen qué es posible. No puedes hacer H-11 antes que H-05 aunque quieras.
  • Las estimaciones son de la lección 14, no salidas de la nada, y ya llevan incorporado el factor de corrección que allí se explica.

  1. El modelo de datos, escrito antes de programar

Aquí está el consejo más rentable de toda la lección: el modelo de datos se escribe antes que el código, en un documento, y se revisa como se revisa un contrato. Cambiar un campo en un documento cuesta treinta segundos. Cambiarlo cuando ya está en el dominio, en el repositorio, en la vista, en las pruebas y en los datos guardados de los usuarios cuesta una tarde y una migración (lección 11-03).

El modelo de Órbita amplía el de Nómada Tareas con tres entidades nuevas.

8.1 Diagrama de entidades

erDiagram
    USUARIO ||--o{ TAREA : "es responsable de"
    USUARIO ||--o{ TAREA : "revisa"
    USUARIO ||--o{ CAMBIO : "realiza"
    TAREA ||--o{ TAREA : "se descompone en"
    TAREA ||--o{ CAMBIO : "acumula"
    TAREA }o--o{ ETIQUETA : "lleva"
    TABLERO ||--o{ TAREA : "contiene"
    TABLERO ||--o{ USUARIO : "reúne"

    USUARIO {
        string id PK
        string nombre
        string iniciales
        string rol "coordinacion|equipo|invitado"
        boolean activo
    }
    TAREA {
        number id PK
        string titulo
        string responsableId FK "null permitido"
        string revisorId FK "null permitido"
        string prioridad "alta|media|baja"
        string estado "pendiente|en-curso|hecha"
        number horasEstimadas
        string fechaLimite "ISO o null"
        number tareaMadreId FK "null si es raiz"
        string creadaEn "ISO"
    }
    ETIQUETA {
        string nombre PK "minusculas"
    }
    CAMBIO {
        string id PK
        number tareaId FK
        string usuarioId FK
        string fecha "ISO con hora"
        string campo
        string antes
        string despues
    }

8.2 Las entidades, campo a campo

Usuario — la entidad nueva más importante, porque cambia el tipo de dos campos que llevabas diez módulos tratando como texto.

Campo Tipo Reglas Nota
id string Único, estable, no reutilizable Texto y no número: los usuarios pueden venir de un sistema externo en v2.0
nombre string No vacío tras trim() Dato personal: ver la advertencia al final del apartado
iniciales string 1–3 caracteres, mayúsculas Para el avatar textual, sin imágenes
rol string 'coordinacion' | 'equipo' | 'invitado' Conjunto cerrado, como prioridad
activo boolean Por defecto true Nunca se borra un usuario: se desactiva. Ver 8.3

Tarea — conserva los ocho campos de Nómada Tareas y añade cuatro:

Campo Cambio respecto a Nómada Tareas
responsableresponsableId Pasa de string a referencia a Usuario.id; sigue admitiendo null (R8)
revisorrevisorId Ídem
tareaMadreId Nuevo. null si es raíz; si no, id de la tarea madre (E2)
creadaEn Nuevo. Fecha ISO con hora; necesaria para R4 y para ordenar el historial
horasEstimadas Ahora es derivado si la tarea tiene hijas: suma de las hojas (R12)
id Sigue siendo number correlativo (R1), ahora único en todo el árbol

Cambio — la entidad del historial (E6). Es append-only: se añade, jamás se modifica ni se borra.

Campo Tipo Nota
id string Un identificador único; puede ser crypto.randomUUID() (07-06)
tareaId number A qué tarea afecta
usuarioId string Quién lo hizo
fecha string ISO con hora Cuándo. Con hora, a diferencia de fechaLimite
campo string Qué campo cambió, o 'creacion' / 'borrado'
antes / despues string | null Valores serializados a texto para que la entrada sea legible sin contexto

Esa última decisión —serializar a texto en lugar de guardar el valor original— es deliberada y conviene entender por qué: una entrada de historial tiene que seguir siendo legible dentro de dos años, cuando el modelo haya cambiado y prioridad quizá ya no exista. Un historial que depende del modelo actual para interpretarse no es un historial: es una copia parcial del estado.

8.3 Las tres decisiones de modelado que hay que tomar ahora

1 · ¿Qué pasa cuando se borra un usuario con tareas asignadas?

Tres opciones, y hay que elegir una explícitamente:

Opción Qué hace Consecuencia
Borrado en cascada Borra sus tareas Inaceptable: se pierde trabajo del equipo
Poner las tareas a null Las desasigna Aceptable, pero se pierde información histórica
Desactivar (elegida) activo: false; sus tareas conservan la referencia Se conserva todo; la interfaz muestra «(inactivo)»

La opción elegida es la tercera, y es la que se usa en casi todo software profesional: el borrado real de entidades referenciadas casi nunca es lo correcto. Esta decisión merece un ADR (lección 11-06).

2 · ¿Las etiquetas son una entidad o un array de textos?

En Nómada Tareas eran un array de textos normalizados (R9). Para el MVP de Órbita se mantienen así, con una función que deriva el catálogo de etiquetas del tablero. Convertirlas en entidad con id propio solo compensa si necesitas renombrar una etiqueta en todas las tareas a la vez, y eso está fuera de alcance.

Anótalo en el documento: una decisión que se toma explícitamente y se justifica no es deuda técnica; es una decisión. Lo que crea deuda es no darse cuenta de que había una elección.

3 · ¿El árbol se guarda anidado o plano con referencia?

Forma Cómo Ventaja Inconveniente
Anidado tarea.subtareas = [...] Se lee y se pinta de forma natural Mover una subtarea implica cortar y pegar en dos sitios; buscar por id obliga a recorrer todo
Plano con tareaMadreId (elegida) Lista plana; el árbol se construye al leer Buscar por id es directo; mover es cambiar un campo; se guarda y se serializa trivialmente Hay que construir el árbol en memoria y detectar ciclos

La forma plana es la que usan las bases de datos y la que te ahorrará problemas en la lección 11-03. Construir el árbol es una función recursiva de quince líneas (03-07); mantener sincronizada una estructura anidada duplicada es un problema permanente.

Aviso de protección de datos. En cuanto tu modelo contiene nombre de personas, estás modelando datos personales. En un proyecto de aprendizaje con datos ficticios no hay problema legal alguno, pero adquiere el hábito ahora: anota en el documento de alcance qué campos son personales, para qué se usan y cuánto tiempo se conservan. Si algún día este producto tratara datos de personas reales, el RGPD exige base legal, información al interesado, minimización y plazos de conservación — y eso requiere asesoramiento legal específico, no una lección de JavaScript. Volveremos sobre ello en 11-03 y 11-05.

  1. Las reglas de negocio nuevas: R11 a R15

Las reglas R1 a R10 de Nómada Tareas siguen vigentes. Las repaso brevemente porque son la base sobre la que se apoyan las nuevas:

Regla Descripción
R1 El id es único y correlativo; lo asigna la aplicación, nunca el usuario
R2 El titulo no puede estar vacío ni contener solo espacios
R3 horasEstimadas mayor que 0 y no superior a 40
R4 fechaLimite no puede ser anterior a la fecha de creación
R5 Una tarea nueva nace siempre en estado 'pendiente'
R6 Solo se permiten las transiciones de estado válidas
R7 Nadie puede superar 40 horas estimadas asignadas en la misma semana
R8 Una tarea sin responsable usa null, nunca ''
R9 Las etiquetas se guardan en minúsculas y sin duplicados
R10 Una tarea vencida (fechaLimite pasada y estado distinto de 'hecha') se destaca

Y estas son las cinco nuevas, que son las que dan carácter a Órbita:

Regla Descripción Dónde se aplica Error que lanza
R11 responsableId y revisorId deben referirse a un usuario existente y activo, o ser null. Una persona no puede ser responsable y revisora de la misma tarea Dominio, al crear y al asignar ErrorDeValidacion con .campo
R12 El árbol de subtareas no admite ciclos y tiene profundidad máxima 3. Las horasEstimadas de una tarea con hijas son la suma de las hojas, nunca un valor propio Dominio, al vincular y al calcular ErrorDeRegla
R13 Una tarea con al menos una subtarea no cerrada no puede pasar a 'hecha'. Al cerrar una tarea madre se registra el cierre de todas sus hojas Dominio, en cambiarEstado ErrorDeRegla
R14 Toda modificación del dominio genera una entrada de historial inmutable. El historial no se edita ni se borra; una corrección es una entrada nueva Aplicación, tras cada mutación — (invariante, no validación)
R15 Un usuario con rol 'invitado' solo puede leer. La carga de R7 se calcula por usuario y semana ISO, contando solo tareas abiertas y solo las horas de las hojas Dominio (carga) y aplicación (permisos) ErrorDeRegla

Cinco observaciones sobre estas reglas, porque cada una esconde una decisión:

R11 y la integridad referencial. «Existente y activo» es más fuerte que «existente». Significa que al desactivar un usuario hay que decidir qué pasa con sus asignaciones futuras: la respuesta elegida es que las existentes se conservan (son historia) pero no se pueden crear nuevas. Esa asimetría es intencionada y merece una prueba explícita.

R12 y la suma de las hojas. Es la regla que evita la doble contabilización del criterio 4 de H-11. Su consecuencia práctica: horasEstimadas deja de ser un campo editable en cuanto una tarea tiene hijas, y la interfaz debe reflejarlo (campo deshabilitado con explicación, no campo que acepta un valor que se ignora en silencio).

R13 y el cierre en cascada. Prohibir cerrar una madre con hijas abiertas es lo estricto; permitirlo cerrando las hijas automáticamente es lo cómodo. La regla elegida hace lo estricto por defecto y ofrece la cascada como acción explícita («Cerrar también las 3 subtareas pendientes»). Nunca hagas la cascada en silencio: borrar o cerrar cosas que el usuario no ha mirado es la receta de la desconfianza.

R14 y la inmutabilidad. «Una corrección es una entrada nueva» es el principio contable de toda la vida, y es lo que hace que un historial valga para algo. En cuanto se puede editar, deja de ser prueba de nada.

R15 y la semana ISO. Elegir la semana ISO 8601 (lunes a domingo, con la regla del jueves para la semana 1) en lugar de «los últimos siete días» es una decisión con consecuencias: hay que implementarla bien, y es un caso de prueba precioso porque el 1 de enero cae a veces en la semana 52 del año anterior. Anótalo: será una de tus pruebas parametrizadas de la lección 11-04.

Dónde viven las reglas. Todas, sin excepción, en la capa de dominio. La vista puede anticiparlas para dar buena información al usuario (deshabilitar un botón, avisar antes de enviar), pero la vista nunca es la que decide. Si una regla solo está en el formulario, no existe: basta con llamar al método desde otro sitio para saltársela. Esto es exactamente lo que 06-07 explicaba sobre validación de cliente, y lo mismo que la lección 11-05 repetirá sobre validación de servidor.

  1. La arquitectura por capas y las fronteras que no se cruzan

Nómada Tareas tenía cuatro carpetas —modelo/, datos/, vista/, util/— y esa organización no era estética: era una arquitectura por capas, con reglas sobre quién puede llamar a quién. Órbita la formaliza con cuatro capas y una capa transversal.

flowchart TD
    subgraph APP["aplicacion/ — orquesta casos de uso"]
        A1["crearTarea()"]
        A2["cambiarEstado()"]
        A3["generarInforme()"]
    end
    subgraph VISTA["vista/ — DOM, eventos, accesibilidad"]
        V1["TableroVista"]
        V2["FormularioTarea"]
        V3["InformeVista"]
    end
    subgraph DATOS["datos/ — persistencia y red"]
        D1["RepositorioMemoria"]
        D2["RepositorioLocal"]
        D3["RepositorioApi"]
    end
    subgraph DOM["dominio/ — reglas R1-R15, sin dependencias"]
        M1["Tarea"]
        M2["Tablero"]
        M3["Usuario"]
        M4["reglas.js"]
    end

    VISTA -->|"eventos"| APP
    APP -->|"lee y escribe"| DOM
    APP -->|"guarda y carga"| DATOS
    DATOS -->|"reconstruye"| DOM
    APP -->|"notifica estado"| VISTA

    style DOM fill:#dcfce7,stroke:#16a34a
    style DATOS fill:#dbeafe,stroke:#2563eb
    style VISTA fill:#fef3c7,stroke:#d97706
    style APP fill:#f3e8ff,stroke:#9333ea

Las cuatro capas, con su responsabilidad y sus prohibiciones:

Capa Responsabilidad Puede importar Nunca importa
dominio/ Las entidades, sus invariantes y las reglas R1–R15 Solo util/ puro datos/, vista/, aplicacion/, fetch, document, localStorage
datos/ Guardar, cargar, hablar con la red, migrar formatos dominio/, util/ vista/, aplicacion/
vista/ Pintar, escuchar eventos, accesibilidad, foco util/, tipos del dominio/ solo para leer datos/, aplicacion/ (recibe funciones, no las importa)
aplicacion/ Casos de uso: orquesta dominio + datos + estado y avisa a la vista Las tres anteriores
util/ Funciones puras: fechas, formato, debounce Nada Todo lo demás

Las tres fronteras que no se cruzan, y qué te pasa si las cruzas:

  1. dominio/ no sabe que existe un navegador. Ni document, ni localStorage, ni fetch, ni alert. Consecuencia práctica inmediata: el dominio se prueba en Node sin jsdom, en milisegundos, y eso es lo que hace posible tener cientos de pruebas rápidas (11-04). Si un día necesitas Date.now() dentro del dominio, se pasa como parámetro o como reloj inyectado — nunca se llama directamente, porque entonces las pruebas dependen de qué día las ejecutes.

  2. vista/ no sabe de dónde vienen los datos. Recibe un estado y unas funciones (alCrearTarea, alCambiarEstado) y las llama. No sabe si detrás hay memoria, localStorage o una API. Consecuencia práctica: cambiar el almacenamiento en la lección 11-03 no tocará ni un fichero de vista.

  3. datos/ no sabe qué se pinta. Devuelve entidades del dominio o lanza errores tipados. No devuelve HTML, ni mensajes de usuario, ni «cadenas listas para mostrar». Consecuencia práctica: la traducción de un error a un texto amable es responsabilidad de la vista, y por eso el mismo ErrorDeRegla puede mostrarse de tres formas distintas en tres pantallas.

Por qué esta arquitectura, y no otra. Dos referencias del propio curso:

  • 05-04 (módulos) enseñó que un import es una dependencia declarada: al escribirlo estás diciendo «esto no funciona sin aquello». Una arquitectura por capas es, literalmente, una regla sobre qué import están permitidos. Y como es una regla mecánica, se puede automatizar: ESLint (08-02) tiene reglas de restricción de importaciones que rompen la compilación si alguien escribe import { guardar } from '../datos/...' dentro de dominio/. Configurarlo es el ejercicio 3.

  • 10-06 demostró con datos que dominio/reglas.js fue idéntico en las cuatro versiones de la misma pantalla (JavaScript puro, React, Vue y Angular). Ese hecho es el argumento definitivo: si el dominio no depende de la vista, sobrevive al cambio de vista. Y las vistas cambian. Siempre.

Un apunte de honestidad: para un proyecto de 100 horas, cuatro capas pueden parecer ceremonia. No lo son, por una razón muy concreta: el coste de introducirlas al principio es de una hora; el de introducirlas cuando ya hay 4.000 líneas mezcladas es de una semana. Y en este proyecto en particular las necesitas, porque la lección 11-03 va a sustituir la capa de datos entera y la 11-04 va a probar cada capa con una técnica distinta.

  1. El entorno: repositorio y cadena de herramientas

Ahora sí, teclado. El objetivo de este apartado es un repositorio que, al ejecutar un solo comando, verifique todo y salga en verde.

11.1 Inicializar

# 1 · Carpeta y repositorio
mkdir orbita && cd orbita
git init -b main

# 2 · package.json (sin preguntas)
npm init -y

# 3 · Entorno de desarrollo y construcción
npm install --save-dev vite

# 4 · Calidad de código (08-02)
npm install --save-dev eslint @eslint/js prettier eslint-config-prettier

# 5 · Pruebas (08-03, 08-05)
npm install --save-dev jest jest-environment-jsdom \
  @testing-library/dom @testing-library/user-event @testing-library/jest-dom

# 6 · Extremo a extremo (08-06)
npm install --save-dev cypress

# 7 · Ganchos de Git
npm install --save-dev husky lint-staged
npx husky init

Cada línea, explicada:

  • git init -b main crea el repositorio con la rama principal llamada main desde el principio, que es lo que espera GitHub y lo que usará el flujo de despliegue de la lección 11-05.
  • vite es el servidor de desarrollo y el empaquetador. Te da recarga instantánea al guardar y, en producción, la compilación con minificación y hashing que verás en 11-05. Es la herramienta que Nómada Tareas usaba desde el Módulo 9.
  • ESLint y Prettier separan responsabilidades como explicaba 08-02: ESLint busca errores, Prettier impone formato. eslint-config-prettier desactiva las reglas de ESLint que se pelearían con Prettier.
  • Jest con jsdom para unitarias e integración; Testing Library para consultar el DOM por rol y por texto, como una persona (08-05).
  • Cypress para los tres recorridos de extremo a extremo (08-06).
  • Husky y lint-staged ejecutan las comprobaciones antes de cada commit, sobre los ficheros que estás confirmando. Es la diferencia entre «la CI me avisará» y «no puedo confirmar código roto».

11.2 package.json completo

{
  "name": "orbita",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "description": "Gestor de trabajo para equipos pequeños. Proyecto final del curso de JavaScript.",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "e2e": "cypress run",
    "e2e:abrir": "cypress open",
    "verificar": "npm run lint && npm run format:check && npm run test:cov && npm run build"
  },
  "lint-staged": {
    "*.js": ["eslint --fix", "prettier --write"],
    "*.{css,html,json,md}": ["prettier --write"]
  }
}

El guion clave es verificar. Es el contrato del proyecto en una línea: lint, formato, pruebas con cobertura y compilación. Es exactamente lo que ejecutará la integración continua (11-04) y lo que debes poder lanzar en cualquier momento para saber si el proyecto está sano. La regla asociada es simple y no admite excepciones:

npm run verificar tiene que estar en verde antes de cada push. Siempre.

El orden de los cuatro pasos no es casual: van de más rápido a más lento, para que el primero que falle sea el más barato de detectar.

11.3 Configuración de ESLint con las fronteras de arquitectura

Este es el fichero eslint.config.js, y contiene la parte que hace que la arquitectura del apartado 10 se cumpla sola:

import js from '@eslint/js';
import prettier from 'eslint-config-prettier';

export default [
  js.configs.recommended,
  prettier,

  // Reglas generales del proyecto
  {
    files: ['src/**/*.js'],
    languageOptions: {
      ecmaVersion: 2023,
      sourceType: 'module',
      globals: { window: 'readonly', document: 'readonly', localStorage: 'readonly' }
    },
    rules: {
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
      eqeqeq: ['error', 'always'],
      'prefer-const': 'error'
    }
  },

  // FRONTERA 1: el dominio no conoce el navegador ni las otras capas
  {
    files: ['src/dominio/**/*.js'],
    languageOptions: {
      globals: {} // ni window, ni document, ni localStorage
    },
    rules: {
      'no-restricted-imports': ['error', {
        patterns: [
          { group: ['**/datos/**'], message: 'El dominio no puede importar de datos/.' },
          { group: ['**/vista/**'], message: 'El dominio no puede importar de vista/.' },
          { group: ['**/aplicacion/**'], message: 'El dominio no puede importar de aplicacion/.' }
        ]
      }],
      'no-restricted-globals': ['error',
        { name: 'document', message: 'El dominio no toca el DOM.' },
        { name: 'localStorage', message: 'El dominio no persiste; eso es datos/.' },
        { name: 'fetch', message: 'El dominio no habla por red; eso es datos/.' }
      ]
    }
  },

  // FRONTERA 2: la vista no conoce la persistencia
  {
    files: ['src/vista/**/*.js'],
    rules: {
      'no-restricted-imports': ['error', {
        patterns: [
          { group: ['**/datos/**'], message: 'La vista recibe datos, no los busca.' }
        ]
      }]
    }
  }
];

Lo que acabas de hacer es importante y merece que lo veas con claridad: has convertido una decisión de arquitectura en una regla automática. A partir de ahora, la frontera no depende de que te acuerdes un viernes por la tarde. Si alguien —tú dentro de tres semanas— escribe import { RepositorioLocal } from '../datos/repositorio-local.js' dentro de dominio/tarea.js, npm run lint falla, el commit se rechaza y la CI se pone en rojo.

Las arquitecturas que no se pueden verificar automáticamente se erosionan siempre. No es cuestión de disciplina: es cuestión de que nadie recuerda un documento de hace dos meses a las once de la noche.

11.4 El gancho de pre-commit

# .husky/pre-commit
npx lint-staged
npm test -- --onlyChanged --passWithNoTests

Dos pasos: formatear y revisar solo los ficheros que estás confirmando (lint-staged), y ejecutar solo las pruebas afectadas (--onlyChanged). Rápido a propósito: un gancho lento es un gancho que acabarás saltándote con --no-verify, y un gancho que se salta no sirve para nada. La suite completa es responsabilidad de la CI.

  1. La estructura de carpetas de partida

orbita/
├── .github/workflows/ci.yml       ← integración continua (11-04)
├── .husky/pre-commit
├── cypress/
│   ├── e2e/                       ← los 3 recorridos (11-04)
│   └── support/
├── docs/
│   ├── alcance.md                 ← MVP + lo que NO se hará
│   ├── modelo-datos.md            ← entidades + R1-R15
│   ├── historias.md               ← historias con criterios
│   └── adr/                       ← decisiones de arquitectura (11-06)
│       └── 0001-arquitectura-por-capas.md
├── public/
│   ├── manifest.json
│   └── iconos/
├── src/
│   ├── main.js                    ← único punto de entrada: monta todo
│   ├── estilos/
│   │   ├── base.css
│   │   └── componentes.css
│   ├── dominio/                   ← SIN dependencias externas
│   │   ├── tarea.js
│   │   ├── usuario.js
│   │   ├── tablero.js
│   │   ├── arbol.js               ← recorrido recursivo (03-07)
│   │   ├── historial.js
│   │   ├── reglas.js              ← R1-R15 en un solo sitio
│   │   └── errores.js             ← ErrorDeValidacion, ErrorDeRegla, ErrorDeDatos
│   ├── datos/
│   │   ├── repositorio.js         ← el CONTRATO (la frontera)
│   │   ├── repositorio-memoria.js
│   │   ├── repositorio-local.js
│   │   ├── migraciones.js         ← (11-03)
│   │   └── semilla.js             ← datos ficticios de ejemplo
│   ├── aplicacion/
│   │   ├── estado.js              ← única fuente de verdad
│   │   ├── casos-uso.js
│   │   └── eventos.js
│   ├── vista/
│   │   ├── dom.js                 ← crearElemento, $, $$
│   │   ├── tablero-vista.js
│   │   ├── formulario-tarea.js
│   │   ├── informe-vista.js
│   │   ├── historial-vista.js
│   │   └── enrutador.js
│   └── util/
│       ├── fechas.js              ← incluida la semana ISO de R15
│       ├── formato.js             ← Intl
│       └── tiempo.js              ← debounce, throttle
├── test/
│   ├── dominio/
│   ├── datos/
│   └── vista/
├── .gitignore
├── .prettierrc
├── eslint.config.js
├── index.html
├── jest.config.js
├── package.json
├── README.md
└── vite.config.js

Tres decisiones de la estructura, con su porqué:

  • src/ como raíz del código. Separa el código de la configuración, que en la raíz ya son diez ficheros. Vite lo espera así por defecto.
  • docs/ versionado con el código. Si el documento de alcance vive en una nota suelta o en un servicio externo, se desincroniza en dos semanas. Dentro del repositorio, cambia en el mismo commit que el código que refleja.
  • test/ fuera de src/, reflejando su estructura. Existe la alternativa de poner las pruebas junto al código (tarea.test.js al lado de tarea.js); las dos funcionan. La ventaja de separarlas es que el build de producción no tiene que excluir nada y la cobertura por capas se lee de un vistazo.

  1. Git en serio: ramas, commits y revisiones

Hasta ahora quizá hayas usado Git como copia de seguridad: git add ., git commit -m "cambios", git push. Funciona hasta que necesitas responder a una de estas preguntas, y entonces no funciona en absoluto:

  • ¿Cuándo dejó de funcionar el informe de carga y qué cambio lo rompió?
  • ¿Por qué está escrito así este trozo raro que no me atrevo a tocar?
  • ¿Puedo deshacer la funcionalidad de subtareas sin deshacer las tres de después?
  • ¿Qué entró exactamente en la versión que desplegué el martes?

El historial de Git es documentación que se escribe sola si le dedicas treinta segundos por commit. Estas son las reglas del proyecto.

13.1 Ramas cortas

Una rama por historia de usuario. Nace de main, vive entre unas horas y tres días, y se fusiona.

git switch -c feat/h04-subtareas       # nace de main
# ... trabajo, varios commits pequeños ...
npm run verificar                      # obligatorio antes de subir
git push -u origin feat/h04-subtareas
# revisión (13.3), fusión, y borrado de la rama

Convenio de nombres, con el mismo prefijo que los commits:

Prefijo Para qué Ejemplo
feat/ Funcionalidad nueva feat/h07-filtro-etiquetas
fix/ Corrección de un fallo fix/horas-duplicadas-en-arbol
refactor/ Cambio interno sin cambio de comportamiento refactor/extraer-reglas-carga
docs/ Solo documentación docs/adr-0003-repositorio
chore/ Herramientas, dependencias, configuración chore/actualizar-vite

Por qué cortas. Una rama de tres semanas acumula conflictos, se desincroniza de main y llega un momento en que fusionarla da miedo. Una rama de dos días se fusiona sin pensar. Si una historia no cabe en tres días, la historia es demasiado grande: pártela (H-04 se puede partir en «crear subtarea», «agregar horas» y «regla de cierre R13»).

13.2 Commits convencionales

El formato de Commits Convencionales es un estándar ampliamente adoptado:

<tipo>(<ámbito opcional>): <descripción en imperativo, minúscula, sin punto>

<cuerpo opcional: el PORQUÉ, no el qué>

<pie opcional: BREAKING CHANGE, referencias>

Ejemplos reales de este proyecto, del peor al mejor:

❌ arreglos
❌ Cambios en el tablero
❌ fix bug

✅ feat(dominio): añadir vinculación de subtareas con detección de ciclos
✅ fix(informe): contar solo las horas de las hojas del árbol
✅ test(dominio): cubrir R13 con las cuatro combinaciones de estado
✅ refactor(vista): extraer la construcción de filas a filaTarea()
✅ docs(adr): registrar la decisión de repositorio plano con tareaMadreId

Y un commit con cuerpo, que es donde está el valor de verdad:

fix(informe): contar solo las horas de las hojas del árbol

El informe de carga sumaba las horas de la tarea madre y también las de
sus subtareas, duplicando el total. Con el tablero de ejemplo daba 71 h
donde el total real es 48 h.

La causa es que horasEstimadas de una madre es derivado (R12) pero
acumularCarga() recorría todas las tareas planas sin distinguir hojas.

Se añade esHoja() en dominio/arbol.js y se filtra antes de acumular.
Prueba de regresión en test/dominio/carga.test.js.

Refs: R12, H-11 criterio 4

Nadie escribe esto para lucirse. Se escribe porque dentro de seis meses, cuando el informe vuelva a dar un número raro, este mensaje aparece en git log y te ahorra dos horas. Es el mismo argumento del historial de cambios de la R14, aplicado al código en lugar de a los datos.

La regla de oro del tamaño del commit: si necesitas la palabra «y» para describir lo que hace, son dos commits.

Qué va en la descripción y qué va en el cuerpo:

Parte Contenido Pregunta que responde
Descripción Qué cambia, en imperativo, ≤ 72 caracteres Qué
Cuerpo Contexto, causa, alternativas descartadas Por qué
Pie Referencias a historias, reglas, incompatibilidades Con qué se relaciona

El qué ya está en el diff. El porqué solo está en tu cabeza, y tu cabeza no se puede consultar dentro de seis meses.

13.3 Revisiones, aunque trabajes solo

«Reviso mi propio código» suena a teatro. No lo es, si se hace con un procedimiento que fuerce el cambio de perspectiva:

  1. Abre una Pull Request contra main, siempre. Aunque la vayas a aprobar tú.
  2. Deja pasar tiempo. Idealmente hasta el día siguiente; como mínimo, una hora haciendo otra cosa. La revisión inmediata no ve nada porque tu cabeza todavía rellena los huecos.
  3. Lee el diff completo en la interfaz web, no en tu editor. El cambio de contexto visual es sorprendentemente eficaz: verás cosas que en tu editor eran invisibles.
  4. Aplica la lista de comprobación del apartado siguiente y escribe los comentarios en la PR, no en un papel.
  5. Corrige con commits nuevos en la misma rama, no reescribiendo. Que se vea la corrección.

Lista de comprobación de revisión (la ampliaremos en 11-02 y 11-06):

# Pregunta Si la respuesta es «no»…
1 ¿El cambio hace una cosa? Pártelo
2 ¿Hay una prueba que falla sin este cambio? Escríbela primero
3 ¿Respeta las fronteras de capas? ESLint ya te lo dijo; hazle caso
4 ¿Los nombres dicen lo que hacen? Renombra; es barato ahora
5 ¿Hay código comentado, console.log o TODO sin fecha? Bórralo
6 ¿Se manejan los casos límite (vacío, null, error)? Añádelos
7 ¿Es accesible por teclado y con lector de pantalla? Vuelve al apartado de accesibilidad
8 ¿El mensaje de commit explica el porqué? Reescríbelo

Y una regla que vale oro cuando alguien más revisa tu código o tú revisas el de otros: critica el código, nunca a la persona. «Esta función hace tres cosas» en lugar de «has hecho un lío». Aplícalo también contigo mismo: el objetivo es un producto mejor, no un juicio.

  1. Hitos y estimación honesta

14.1 El problema de la estimación

Todo el mundo estima mal y siempre en la misma dirección: por defecto. La causa está bien estudiada y se llama falacia de la planificación: al estimar imaginamos el camino en el que todo sale bien, porque es el único que podemos imaginar en detalle. Los imprevistos, por definición, no se pueden enumerar.

El método honesto tiene tres pasos:

  1. Estima cada historia en horas de trabajo efectivo, suponiendo que no hay interrupciones y que sabes hacerlo.
  2. Multiplica por un factor de corrección según lo conocido que te resulte el problema.
  3. Añade un 20 % de reserva al total del proyecto para lo que no está en ninguna historia (configurar cosas, arreglar el despliegue, un fallo raro de Jest).
Nivel de familiaridad Factor Ejemplo en Órbita
Lo he hecho varias veces × 1,3 Formulario con validación (06-07)
Lo he hecho una vez guiado × 2 Persistencia con migraciones (07-01, pero las migraciones son nuevas)
Lo entiendo pero nunca lo he hecho solo × 3 Árbol de subtareas con reglas R12/R13
No sé por dónde empezar × 4 o investiga primero Exportación a CSV con caracteres especiales

Un ejemplo completo, la historia H-04:

Concepto Horas
Estimación ingenua («un árbol, medio día») 4
Factor × 3 (nunca has implementado un árbol con reglas de negocio) 12
Ajuste a la baja: el MVP solo expone un nivel 10

Las 10 horas de la tabla del apartado 7 salen de ahí, no de una intuición.

Total de Órbita: 105 h de historias + 21 h de reserva (20 %) = 126 horas. A 8 horas semanales, unas 16 semanas. Si eso te parece mucho, tienes dos salidas legítimas: dedicarle más horas por semana, o recortar los Could (22 h) y quedarte en 13 semanas. Lo que no es una salida es decidir que en realidad serán 60 horas.

14.2 Los hitos

Un hito no es una fecha: es un estado del producto que se puede enseñar. Cada uno termina con algo demostrable.

Hito Qué se puede enseñar al terminar Historias Horas Lección
H1 · Cimientos Repositorio verde: npm run verificar pasa, con una prueba trivial y la página vacía desplegable 10 11-01
H2 · Dominio vivo El dominio con R1–R15 y sus pruebas en verde, ejercitable desde la consola H-01…H-03 25 11-02
H3 · Vertical completa Crear, ver, asignar y descomponer una tarea de punta a punta en el navegador H-04…H-06 24 11-02
H4 · Datos que duran Filtros y persistencia local con migraciones; recargar no pierde nada H-07…H-09 22 11-03
H5 · Producto usable Historial, informe, estados vacíos y recorrido por teclado H-10…H-13 26 11-04
H6 · Publicado Desplegado con HTTPS, CI en verde, README y demo H-14…H-16 19 11-05, 11-06
gantt
    title Órbita — plan de 16 semanas a 8 h/semana
    dateFormat YYYY-MM-DD
    axisFormat S%W

    section Cimientos
    H1 · Repositorio, herramientas, CI     :h1, 2026-09-21, 10d
    section Dominio
    H2 · Entidades, reglas R11-R15, TDD    :h2, after h1, 22d
    section Producto
    H3 · Vertical completa punta a punta   :h3, after h2, 21d
    H4 · Filtros y persistencia            :h4, after h3, 19d
    H5 · Historial, informe, accesibilidad :h5, after h4, 23d
    section Entrega
    H6 · Despliegue y documentación        :h6, after h5, 17d
    Reserva del 20 %                       :res, after h6, 21d

Dos observaciones sobre el Gantt:

  • La reserva aparece en el diagrama. Si la reserva no está dibujada, no existe: se la comerá el primer imprevisto y darás por perdido el plan. Dibujada, es una parte del plan que se está consumiendo, y lo ves.
  • Los hitos no se solapan. Es tentador dibujar tareas en paralelo, pero trabajas solo: el paralelismo es una ilusión que solo sirve para que el plan parezca más corto.

Cómo se usa el plan. No como una promesa, sino como un instrumento de medida. Al final de cada semana anota horas dedicadas y historias cerradas. Si al terminar H2 llevas un 40 % más de tiempo del previsto, tu factor de corrección es bajo: súbelo para el resto del plan en vez de esperar recuperar. Recuperar tiempo perdido casi nunca ocurre; ajustar el modelo, sí.

  1. La Definición de Hecho

La Definición de Hecho (Definition of Done) responde a una pregunta que parece tonta y no lo es: ¿cuándo está terminada una historia? Sin una respuesta escrita, «hecho» significa «funciona en mi máquina cuando hago lo que espero», y ese es el origen del 80 % de la deuda de un proyecto personal.

Se escribe una vez, al principio, y se aplica a todas las historias sin excepción:

## Definición de Hecho (v1.0)

Una historia está HECHA cuando todo lo siguiente es cierto:

### Funcionalidad
- [ ] Todos sus criterios de aceptación se cumplen y se han comprobado a mano
- [ ] Los casos límite tienen comportamiento definido: lista vacía, valor
      `null`, texto muy largo, número fuera de rango, error de red
- [ ] Los estados vacío, de carga y de error están implementados

### Código
- [ ] `npm run verificar` en verde
- [ ] Respeta las fronteras de capas (ESLint no protesta)
- [ ] Sin `console.log`, sin código comentado, sin `TODO` sin fecha
- [ ] Los nombres están en el idioma del proyecto y son coherentes

### Pruebas
- [ ] Pruebas unitarias del dominio afectado
- [ ] Al menos una prueba de integración por criterio de aceptación
      que implique interfaz
- [ ] Todas las reglas de negocio tocadas tienen prueba del caso que
      SÍ pasa y del caso que NO
- [ ] La cobertura del dominio no baja del 90 %

### Accesibilidad
- [ ] Recorrido completo con teclado, con foco siempre visible
- [ ] Elementos semánticos correctos (nada de `<div>` pulsable)
- [ ] Los cambios relevantes se anuncian con `aria-live`
- [ ] Contraste mínimo 4,5:1 en texto normal
- [ ] axe no reporta ninguna incidencia de nivel grave

### Rendimiento
- [ ] Dentro del presupuesto del apartado 16
- [ ] Sin fugas de memoria al entrar y salir de la pantalla tres veces

### Documentación
- [ ] README actualizado si cambia la instalación o el uso
- [ ] ADR escrito si se ha tomado una decisión de arquitectura
- [ ] Mensaje de commit con el porqué

Sí, es larga. Y sí, se cumple, por dos motivos:

  1. La mayoría de las casillas las verifica una máquina: npm run verificar cubre siete de ellas. Solo unas pocas exigen que tú hagas algo a mano.
  2. Es más barata que la alternativa. Cada casilla de esta lista representa un problema real que aparece si no se comprueba, y todos son más caros de arreglar después.

Un consejo práctico: guárdala como plantilla de Pull Request en .github/pull_request_template.md. Así aparece sola, marcada como lista de tareas, cada vez que abres una PR. Lo que no aparece solo, no se hace.

  1. El presupuesto de rendimiento y accesibilidad

La lección 09-01 dejó una idea que hay que aplicar ahora, no al final: medir antes de optimizar. Y su corolario, que es lo que hace este apartado: un objetivo sin número no es un objetivo.

Nómada Tareas midió su línea base al final, con la aplicación ya construida, y encontró diez problemas de golpe. Tú vas a hacerlo al revés: fijas el presupuesto ahora, con la aplicación vacía, y la CI lo vigila desde el primer día. La diferencia es enorme: un presupuesto fijado al principio se incumple el día que se rompe, con un solo cambio sospechoso; fijado al final, se incumple por la acumulación de treinta cambios y nadie sabe cuál fue.

16.1 Presupuesto de rendimiento

La columna de referencia son los números medidos de Nómada Tareas en la lección 09-01 (línea base con 600 tareas, tras las optimizaciones de todo el Módulo 9):

# Métrica Cómo se mide Presupuesto de Órbita Nómada Tareas (ref.)
1 JS inicial (comprimido) Vite build / Network ≤ 60 kB 58,3 kB
2 Peticiones para la primera pantalla Network ≤ 4 3
3 LCP (móvil simulado, Slow 4G, CPU 4×) Lighthouse CI ≤ 2,5 s 1,9 s
4 CLS Lighthouse CI ≤ 0,1 0,02
5 INP al filtrar Performance, Interactions ≤ 200 ms 42 ms
6 render() con 500 tareas User Timing ≤ 50 ms 31 ms (600 tareas)
7 Nodos DOM del documento Performance, contador ≤ 1.500 1.194
8 Memoria retenida tras 3 ciclos de navegación Memory, 3 instantáneas ≈ 0 sin fugas
9 Puntuación de rendimiento de Lighthouse Lighthouse CI ≥ 90

Los presupuestos son algo más laxos que los números de Nómada Tareas, y eso es deliberado: Órbita tiene más pantallas y más funcionalidad. Un presupuesto imposible se ignora a la primera semana; uno alcanzable pero exigente se respeta.

La regla del presupuesto: si un cambio lo incumple, hay tres salidas legítimas — optimizar el cambio, renunciar a la funcionalidad, o subir el presupuesto conscientemente y anotar por qué. La cuarta salida, «lo miro más adelante», es la que produce aplicaciones de 2 MB.

16.2 Presupuesto de accesibilidad

# Criterio Cómo se comprueba Umbral
1 Sin incidencias graves de axe axe automatizado en CI 0
2 Puntuación de accesibilidad de Lighthouse Lighthouse CI ≥ 95
3 Todo alcanzable con teclado Manual, por pantalla 100 %
4 Foco siempre visible Manual + CSS :focus-visible Siempre
5 Contraste de texto normal axe / DevTools ≥ 4,5:1
6 Contraste de texto grande y elementos de interfaz axe / DevTools ≥ 3:1
7 Usable con zoom al 200 % Manual Sin pérdida de contenido ni de función
8 Cambios importantes anunciados Manual con lector de pantalla aria-live presente
9 Ninguna información solo por color Revisión manual 0 casos

El criterio 9 tiene una consecuencia inmediata en Órbita y conviene verla ahora: la tarea vencida de la R10 no puede destacarse solo en rojo, y la celda sobrecargada de la R7 no puede destacarse solo en ámbar. Las dos necesitan texto o icono con texto alternativo. Si lo sabes ahora, lo diseñas bien; si lo descubres en la revisión de accesibilidad, lo reharás.

Los umbrales 1, 2, 5 y 6 los verifica la máquina. Los demás son manuales y van en la Definición de Hecho. La lección 11-04 monta la automatización completa.

  1. La plantilla de README.md

El README.md es lo primero que ve cualquiera que llegue a tu repositorio: un revisor, un reclutador, o tú mismo dentro de un año. Se crea ahora, con huecos, y se rellena a medida que el proyecto avanza. Un README que se escribe el último día se nota, y no se nota bien.

# Órbita

Gestor de trabajo para equipos pequeños: tareas, subtareas, carga por
persona e historial de cambios. Proyecto final del curso de JavaScript,
construido **sin frameworks**.

🔗 **Demo:** https://<usuario>.github.io/orbita/
📸 *(captura o GIF corto de 10 s aquí)*

---

## Qué problema resuelve

Un equipo de tres o cuatro personas necesita saber quién hace qué, qué
está bloqueado y quién está sobrecargado. Las herramientas grandes
sobran; una hoja de cálculo se queda corta en cuanto hay reglas.

## Qué hace

- Tareas con estado, prioridad, etiquetas, responsable y fecha límite
- **Subtareas** con horas y progreso agregados
- **Filtro múltiple** por etiquetas (modo *todas* / *cualquiera*),
  responsable y estado, con el filtro guardado en la URL
- **Informe de carga** por persona y semana ISO, con aviso de sobrecarga
- **Historial inmutable** de todos los cambios
- Funciona **sin conexión** e instalable como PWA

## Decisiones técnicas

| Decisión | Por qué | ADR |
|---|---|---|
| Sin framework | El producto es una única SPA con lógica de dominio rica; el coste de mantener la infraestructura propia es asumible | [ADR-0002](docs/adr/0002-sin-framework.md) |
| Arquitectura por capas | El dominio debe poder probarse sin navegador y sobrevivir a un cambio de vista | [ADR-0001](docs/adr/0001-arquitectura-por-capas.md) |
| Árbol plano con `tareaMadreId` | Buscar y mover son O(1); serializa trivialmente | [ADR-0003](docs/adr/0003-arbol-plano.md) |
| `localStorage` con migraciones numeradas | Volumen previsto < 1 MB; migrar sin perder datos | [ADR-0004](docs/adr/0004-persistencia.md) |

## Cómo ejecutarlo

git clone https://github.com//orbita.git cd orbita npm install npm run dev # http://localhost:5173

## Cómo probarlo

npm test # unitarias e integración npm run test:cov # con informe de cobertura npm run e2e # recorridos de extremo a extremo npm run verificar # todo lo anterior + lint + build

## Arquitectura

src/dominio/ Reglas R1-R15. Sin dependencias. Se prueba en Node. src/datos/ Persistencia y red. Una interfaz, tres implementaciones. src/vista/ DOM, eventos y accesibilidad. No sabe de dónde vienen los datos. src/aplicacion/ Casos de uso y estado. Une las tres anteriores.

## Limitaciones conocidas

- Un solo dispositivo: sin sincronización entre navegadores (v2.0)
- Sin autenticación: los datos viven en el navegador y **no son privados**
- Profundidad de subtareas limitada a un nivel en la interfaz
- Probado en Chrome, Firefox y Safari recientes; sin soporte de IE

## Hoja de ruta

- [ ] v1.1 · Vista de calendario y exportación a CSV
- [ ] v1.2 · Tres niveles de subtareas
- [ ] v2.0 · Backend con autenticación y sincronización

## Licencia

MIT — ver [LICENSE](LICENSE).

Las secciones que casi nadie escribe y que más impresionan a quien revisa son «Decisiones técnicas» y «Limitaciones conocidas». La primera demuestra criterio; la segunda demuestra honestidad y conciencia de los límites del propio trabajo, que es exactamente lo que se busca en alguien con quien vas a trabajar. La lección 11-06 desarrolla el README a fondo.

Errores Comunes y Consejos

Empezar a programar antes de tener el modelo de datos escrito. Es el error más caro de todos, porque no duele al principio: duele en la semana cuatro, cuando descubres que responsable debía ser una referencia y ya está en veinte sitios. Media hora de documento ahorra dos días de refactorización. Escribe el modelo, léelo en voz alta, y solo entonces abre el editor.

Confundir MVP con «versión cutre». Un MVP tiene menos funcionalidades, no menos calidad. Las que están, están terminadas: probadas, accesibles y con sus estados vacío y de error. Una aplicación con tres funcionalidades impecables demuestra muchísimo más que una con doce a medias — y en una entrevista, la segunda juega en tu contra.

No escribir la lista de lo que NO se hará. Sin esa lista, cada idea nueva parece razonable porque nada dice lo contrario. Con la lista, añadir algo exige tacharlo explícitamente, y ese pequeño acto de fricción filtra el 90 % de las ideas de martes por la tarde.

Estimar en el mejor caso. Tu estimación ingenua es el tiempo que tardarías si todo saliera bien y ya supieras hacerlo. Multiplica por el factor de la tabla del apartado 14 sin negociar contigo mismo, y añade la reserva del 20 %. Un plan que se cumple motiva; uno que se incumple desde la segunda semana se abandona.

Dejar la arquitectura como una intención. «Ya tendré cuidado de no importar datos/ desde dominio/» es una frase que caduca en dos semanas. Configura la regla de ESLint del apartado 11.3 el primer día: convierte tu buena intención en un error de compilación, que es lo único que no se olvida.

Aplazar la accesibilidad a «cuando funcione». Añadir accesibilidad al final es rehacer la mitad de la vista: los <div> pulsables hay que convertirlos en <button>, el foco hay que gestionarlo, los anuncios hay que introducirlos. Hecho desde el principio no cuesta prácticamente nada, porque casi todo se reduce a usar el elemento HTML correcto.

Un README vacío durante meses. Escribe hoy la primera versión, aunque la mitad sean huecos. Cada vez que tomes una decisión, añade una fila a la tabla de decisiones técnicas. Reconstruir el porqué de veinte decisiones el último día es imposible: no las recordarás.

Ramas largas. Una rama de tres semanas es un proyecto paralelo que un día tendrás que reconciliar. Si una historia no cabe en tres días, pártela. Si no sabes cómo partirla, es señal de que no la entiendes bien todavía — y eso también es información valiosa.

Consejo · Escribe el documento de alcance para «el tú de dentro de tres meses». Ese lector no recuerda nada, no tiene contexto y solo tiene diez minutos. Si el documento le sirve, te sirve a ti y le servirá a cualquiera.

Consejo · Fija un día y una hora de trabajo, y protégelos. La causa número uno de abandono de proyectos personales no es la dificultad técnica: es la pérdida de ritmo. Dos sesiones semanales fijas de tres horas rinden muchísimo más que «cuando pueda».

Consejo · Haz el primer despliegue con la aplicación vacía. En el hito H1, antes de escribir lógica. Descubrirás los problemas de configuración cuando no cuestan nada, en lugar de la última semana con todo en juego. Es el mismo principio que la fase piloto de las migraciones de 10-06.

Ejercicios

Estos tres ejercicios son el hito H1 de tu proyecto. Al terminarlos tendrás el documento de alcance, el modelo de datos y un repositorio verde: todo lo necesario para empezar a construir en la lección siguiente.

Ejercicio 1 — El documento de alcance.

Crea docs/alcance.md en tu repositorio con estas siete secciones:

  1. El producto en una frase (formato del apartado 2): quién lo usa, qué hace, qué problema resuelve.
  2. El MVP: la tabla de funcionalidades con la columna «¿MVP?» y su razón. Mínimo 12 candidatas, y al menos 5 con «No».
  3. Fuera de alcance: la tabla de tres columnas del apartado 5, con al menos 8 filas.
  4. Historias de usuario: entre 12 y 18, con la plantilla Como / quiero / para. Al menos cuatro desarrolladas por completo con criterios Dado / Cuando / Entonces (mínimo 3 criterios cada una), incluyendo al menos un criterio de accesibilidad.
  5. Priorización MoSCoW: la tabla del apartado 7 con estimación, hito y dependencias. Verifica la regla del 60 %.
  6. Plan de hitos: los seis hitos con su entregable demostrable y su diagrama de Gantt en Mermaid, con la reserva dibujada.
  7. Definición de Hecho: adaptada a tu proyecto, con al menos 18 casillas repartidas entre las seis categorías.

Ejercicio 2 — El modelo de datos y las reglas.

Crea docs/modelo-datos.md con:

  1. Un diagrama de entidades en Mermaid (erDiagram) con todas tus entidades y sus relaciones.
  2. Una tabla por entidad con campo, tipo, si admite null, reglas y una nota justificando el tipo elegido.
  3. La tabla de reglas R1–R15 (o las que correspondan a tu dominio, si has elegido otro) con: descripción, capa donde se aplica y error que lanza.
  4. Las tres decisiones de modelado del apartado 8.3 aplicadas a tu dominio, cada una con sus alternativas descartadas y el porqué.
  5. Un ejemplo completo de datos ficticios en JSON con al menos 8 entidades, incluyendo un caso límite de cada regla nueva.
  6. Una sección «Datos personales» que enumere qué campos lo son, para qué se usan y cuánto se conservan.

Ejercicio 3 — El repositorio verde.

Deja el repositorio en el estado exacto del que parte la lección 11-02:

  1. Repositorio con main, primer commit convencional y .gitignore correcto (node_modules, dist, .env, coverage, cypress/videos).
  2. package.json con los diez guiones del apartado 11.2, incluido verificar.
  3. eslint.config.js con las dos fronteras de arquitectura configuradas y comprobadas.
  4. .prettierrc, jest.config.js (con testEnvironment: 'jsdom' y umbrales de cobertura) y vite.config.js.
  5. Gancho de pre-commit con Husky y lint-staged, verificado con un commit real.
  6. La estructura de carpetas del apartado 12, con un .gitkeep en las vacías.
  7. Una prueba que demuestre que las fronteras funcionan: escribe src/dominio/prueba-frontera.js con import ... from '../datos/repositorio.js', comprueba que npm run lint falla con tu mensaje personalizado, y bórralo. Documenta el resultado en el ADR-0001.
  8. README.md con la plantilla del apartado 17 (los huecos se rellenan luego).
  9. npm run verificar en verde.

Soluciones

No hay soluciones en forma de código, porque el proyecto es tuyo. Estas son las rúbricas con las que se evalúa cada entregable. Puntúate honestamente: el objetivo no es la nota, es detectar lo que falta antes de que te cueste caro.

Rúbrica del ejercicio 1 — Documento de alcance (30 puntos)

Criterio 0 · Insuficiente 1 · Aceptable 2 · Bueno 3 · Excelente
Frase del producto No existe o describe la implementación Dice qué hace Dice quién, qué y para qué Además distingue de alternativas obvias
Recorte del MVP Todo es MVP Se descarta algo ≥ 5 «No» razonados Cada «No» pasa la prueba de la pregunta del apartado 4
Fuera de alcance No existe Lista de cosas Tabla con porqué Además con «cuándo se reconsidera» realista
Historias Vagas o descritas como tareas técnicas Formato correcto ≥ 4 con criterios verificables Los criterios se traducen directamente a pruebas
Accesibilidad en criterios Ausente Mencionada aparte ≥ 1 criterio de aceptación Presente en todas las historias con interfaz
MoSCoW Sin priorizar Categorizado Con estimación y dependencias Cumple la regla del 60 % y lo demuestra con el cálculo
Estimación Sin factor de corrección Con factor uniforme Factor por familiaridad Además con reserva del 20 % dibujada en el Gantt
Hitos Solo fechas Con entregables Cada hito es demostrable Ordenados por dependencia real, sin solapes falsos
Definición de Hecho No existe Lista corta ≥ 18 casillas en 6 categorías La mayoría verificables por máquina
Utilidad para un tercero Incomprensible sin ti Se entiende con esfuerzo Se entiende solo Alguien podría continuarlo sin hablar contigo

Umbral de aprobación: 20/30, con al menos 2 puntos en «Recorte del MVP» y en «Historias». Si fallas ahí, todo lo demás se construye sobre arena.

Rúbrica del ejercicio 2 — Modelo de datos (24 puntos)

Criterio Insuficiente (0) Aceptable (1) Bueno (2) Excelente (3)
Entidades Faltan relaciones Todas presentes Con cardinalidades correctas Diagrama Mermaid legible y comentado
Tipos Ambiguos o «string» para todo Definidos Con justificación Además con conjuntos cerrados explícitos
Nulabilidad Sin especificar Marcada Con criterio consistente Distingue «no aplica» de «desconocido»
Reglas nuevas Menos de 3 5 reglas enunciadas Con capa y error asignados Cada una con su caso límite documentado
Decisión de borrado No contemplada Elegida Con alternativas descartadas Con consecuencias en la interfaz descritas
Estructura del árbol Sin decidir Elegida Justificada Con el coste de las operaciones analizado
Datos de ejemplo Ausentes Presentes Con casos límite Reutilizables directamente como semilla de pruebas
Datos personales No mencionados Identificados Con finalidad Además con plazo de conservación y minimización

Señal de alarma inequívoca: si tu tabla de reglas tiene menos de cinco reglas nuevas propias del dominio, tu producto es un CRUD. Añade reglas duras — son lo que hace interesante el dominio, lo que da sentido a las pruebas de 11-04 y lo que se cuenta bien en una entrevista.

Rúbrica del ejercicio 3 — Repositorio (verificación binaria)

Aquí no hay grises: cada punto se cumple o no se cumple.

# Comprobación Comando Esperado
1 El repositorio existe y tiene main git branch --show-current main
2 El primer commit es convencional git log --oneline -1 Empieza por chore: o feat:
3 .gitignore es correcto git status --short Sin node_modules/ ni dist/
4 Los guiones existen npm run Los 10 del apartado 11.2
5 El lint pasa npm run lint Sin errores
6 El formato está aplicado npm run format:check Sin diferencias
7 Las pruebas pasan npm test Al menos 1 prueba, 0 fallos
8 La compilación funciona npm run build dist/ generado
9 El gancho se dispara commit con un fallo de lint El commit se rechaza
10 La frontera funciona añadir el import prohibido npm run lint falla con tu mensaje
11 Todo junto npm run verificar Verde

El punto 10 es el que de verdad importa y el que casi nadie comprueba. Una regla de ESLint mal configurada no falla: simplemente no hace nada, y tú crees que estás protegido durante tres meses. Provoca el error a propósito, comprueba el mensaje, y solo entonces bórralo.

Autoevaluación final del hito H1. Responde con sinceridad antes de pasar a la lección siguiente:

Pregunta Sí / No
¿Puedo describir mi producto en una frase sin dudar?
¿Sé exactamente qué NO voy a construir?
¿Podría escribir hoy la primera prueba de una regla sin inventarme el modelo?
¿Mi plan tiene reserva, o he supuesto que todo saldrá bien?
¿npm run verificar está en verde ahora mismo?
¿Sabría explicar mis fronteras de capas a otra persona en dos minutos?

Seis «sí» y puedes seguir. Un solo «no» y merece la pena volver antes de escribir código, porque cada uno de esos «no» se multiplica por semanas de trabajo más adelante.

Conclusión

Has hecho lo que casi nadie hace en un proyecto personal: planificar antes de programar. Y no con un plan decorativo, sino con instrumentos que se van a usar cada semana del módulo.

Sabes que a partir de aquí el código lo escribes tú: Nómada Tareas deja de ser el ejemplo que se te da resuelto y pasa a ser la referencia contra la que comparar, con sus seis tareas, sus 48 horas, sus reglas R1–R10 y sus números medidos. Tienes un producto por defecto —Órbita— con seis extensiones que obligan a resolver problemas que el curso esquivaba: usuarios como entidades, un árbol de subtareas, filtro múltiple, una vista que no es una lista, un informe que produce un fichero y un historial inmutable que crece sin parar. Y tienes tres alternativas de dominio —Aforo, Racha, Estantería— con el mapeo completo de las seis extensiones, porque el método es el mismo y lo que importa es que te importe.

Sabes recortar: el MVP es lo que no puede quitarse sin romper la frase del producto, y la prueba es una sola pregunta. De quince candidatas entraron ocho. Y sabes que la mitad que se queda fuera hay que escribirla, con su porqué y su cuándo se reconsidera, porque una funcionalidad que no está en ninguna lista es una funcionalidad que aparecerá un martes por la tarde.

Sabes escribir historias de usuario que dicen para quién y para qué, con criterios Dado / Cuando / Entonces tan precisos que se traducen en pruebas casi palabra por palabra — incluidos los criterios de accesibilidad, que van dentro de la historia y no en una lista aparte que se revisa al final. Y sabes priorizarlas con MoSCoW respetando la regla del 60 %, que es la que convierte un plan en un plan y no en una apuesta.

Tienes el modelo de datos escrito antes que el código, con su diagrama de entidades, sus tipos justificados y las tres decisiones que había que tomar explícitamente: desactivar usuarios en lugar de borrarlos, etiquetas como texto normalizado, y árbol plano con tareaMadreId en vez de anidado. Y tienes las reglas nuevas R11 a R15 —integridad referencial con usuarios activos, árbol sin ciclos con horas por hojas, cierre bloqueado por subtareas abiertas, historial inmutable, y permisos con carga por semana ISO— viviendo todas en el dominio, porque una regla que solo está en el formulario no existe.

Tienes una arquitectura por capas con tres fronteras que no se cruzan —el dominio no sabe que hay navegador, la vista no sabe de dónde vienen los datos, los datos no saben qué se pinta— y, lo que es más importante, las tienes verificadas por ESLint: has convertido una intención en un error de compilación, que es lo único que no se erosiona. El argumento no es teórico: en 10-06, dominio/reglas.js fue idéntico en las cuatro versiones de la misma pantalla.

Tienes el entorno montado: Vite, ESLint y Prettier, Jest con jsdom y Testing Library, Cypress, Husky y lint-staged, con un guion verificar que resume el contrato del proyecto en una línea y una regla sin excepciones. Tienes Git en serio: ramas cortas por historia, commits convencionales cuyo cuerpo explica el porqué —lo único que el diff no puede contarte dentro de seis meses— y revisiones de tu propio código con distancia, en la interfaz web y con lista de comprobación.

Tienes un plan honesto: 105 horas de historias, factor de corrección por familiaridad, 20 % de reserva dibujada en el Gantt, seis hitos que terminan cada uno en algo que se puede enseñar. Tienes una Definición de Hecho de la que la mayoría de casillas las verifica una máquina, guardada como plantilla de Pull Request para que aparezca sola. Y tienes el presupuesto de rendimiento y accesibilidad fijado al principio, con los números de Nómada Tareas como referencia: ≤ 60 kB, LCP ≤ 2,5 s, ≤ 1.500 nodos, 0 incidencias graves de axe, contraste 4,5:1 y nada comunicado solo por color.

El hito H1 está cerrado: documento de alcance, modelo de datos con sus reglas, y un repositorio que responde en verde. La página está vacía, y eso es exactamente lo correcto, porque ahora sabes qué vas a poner en ella y por qué.

Lo siguiente es ponerlo. Y hay una forma de hacerlo que no es la intuitiva —de dentro hacia fuera, empezando por las reglas y terminando por los píxeles, y una funcionalidad completa antes que todas a medias—: es Construcción del Proyecto.

Curso de JavaScript: De Principiante a Avanzado

Módulo 1: Introducción a JavaScript

Módulo 2: Estructuras de Control

Módulo 3: Funciones

Módulo 4: Objetos y Arrays

Módulo 5: Objetos y Funciones Avanzadas

Módulo 6: El Modelo de Objetos del Documento (DOM)

Módulo 7: APIs del Navegador y Temas Avanzados

Módulo 8: Pruebas y Depuración

Módulo 9: Rendimiento y Optimización

Módulo 10: Frameworks y Librerías de JavaScript

Módulo 11: Proyecto Final

© Copyright 2026. Todos los derechos reservados