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
- Qué cambia a partir de ahora
- El producto por defecto: Órbita
- Tres alternativas de dominio con el mismo método
- Delimitar el alcance: el MVP
- La lista de lo que NO se hará
- Historias de usuario con criterios de aceptación
- Priorizar con MoSCoW
- El modelo de datos, escrito antes de programar
- Las reglas de negocio nuevas: R11 a R15
- La arquitectura por capas y las fronteras que no se cruzan
- El entorno: repositorio y cadena de herramientas
- La estructura de carpetas de partida
- Git en serio: ramas, commits y revisiones
- Hitos y estimación honesta
- La Definición de Hecho
- El presupuesto de rendimiento y accesibilidad
- La plantilla de
README.md - Errores Comunes y Consejos
- Ejercicios
- Conclusión
- 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.
- 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.
- 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:
- 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.
- Que puedas enseñárselo a alguien. Si en una entrevista puedes explicar el dominio en treinta segundos, sirve.
- Que tenga al menos una regla dura no trivial. Un CRUD sin reglas no demuestra nada; es el proyecto que hace todo el mundo.
- 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.
- 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 | Sí | Sin esto no hay producto |
| Cambiar de estado con las transiciones válidas (R6) | Sí | Es el flujo de trabajo entero |
| Asignar responsable (E1) | Sí | «Quién hace qué» está en la frase del producto |
| Subtareas de un nivel (E2 parcial) | Sí | La descomposición es el diferencial; tres niveles pueden esperar |
| Filtro por responsable, estado y etiquetas (E3) | Sí | Sin filtro, con 60 tareas el tablero es inútil |
| Persistencia local | Sí | Un gestor que pierde los datos al recargar no es viable |
| Historial de cambios (E6) | Sí | 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.
- 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).
- 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:
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.
- 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.
- 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 |
|---|---|
responsable → responsableId |
Pasa de string a referencia a Usuario.id; sigue admitiendo null (R8) |
revisor → revisorId |
Í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
nombrede 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.
- 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.
- 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:
-
dominio/no sabe que existe un navegador. Nidocument, nilocalStorage, nifetch, nialert. 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 necesitasDate.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. -
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,localStorageo una API. Consecuencia práctica: cambiar el almacenamiento en la lección 11-03 no tocará ni un fichero de vista. -
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 mismoErrorDeReglapuede 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
importes una dependencia declarada: al escribirlo estás diciendo «esto no funciona sin aquello». Una arquitectura por capas es, literalmente, una regla sobre quéimportestá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 escribeimport { guardar } from '../datos/...'dentro dedominio/. Configurarlo es el ejercicio 3. -
10-06 demostró con datos que
dominio/reglas.jsfue 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.
- 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 initCada línea, explicada:
git init -b maincrea el repositorio con la rama principal llamadamaindesde el principio, que es lo que espera GitHub y lo que usará el flujo de despliegue de la lección 11-05.vitees 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-prettierdesactiva las reglas de ESLint que se pelearían con Prettier. - Jest con
jsdompara 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 verificartiene que estar en verde antes de cadapush. 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
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.
- 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.jsTres 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 desrc/, reflejando su estructura. Existe la alternativa de poner las pruebas junto al código (tarea.test.jsal lado detarea.js); las dos funcionan. La ventaja de separarlas es que elbuildde producción no tiene que excluir nada y la cobertura por capas se lee de un vistazo.
- 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 ramaConvenio 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 tareaMadreIdY 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 4Nadie 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:
- Abre una Pull Request contra
main, siempre. Aunque la vayas a aprobar tú. - 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.
- 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.
- Aplica la lista de comprobación del apartado siguiente y escribe los comentarios en la PR, no en un papel.
- 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.
- 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:
- Estima cada historia en horas de trabajo efectivo, suponiendo que no hay interrupciones y que sabes hacerlo.
- Multiplica por un factor de corrección según lo conocido que te resulte el problema.
- 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í.
- 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:
- La mayoría de las casillas las verifica una máquina:
npm run verificarcubre siete de ellas. Solo unas pocas exigen que tú hagas algo a mano. - 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.
- 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.
- La plantilla de
README.md
README.mdEl 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/
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
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:
- El producto en una frase (formato del apartado 2): quién lo usa, qué hace, qué problema resuelve.
- El MVP: la tabla de funcionalidades con la columna «¿MVP?» y su razón. Mínimo 12 candidatas, y al menos 5 con «No».
- Fuera de alcance: la tabla de tres columnas del apartado 5, con al menos 8 filas.
- 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.
- Priorización MoSCoW: la tabla del apartado 7 con estimación, hito y dependencias. Verifica la regla del 60 %.
- Plan de hitos: los seis hitos con su entregable demostrable y su diagrama de Gantt en Mermaid, con la reserva dibujada.
- 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:
- Un diagrama de entidades en Mermaid (
erDiagram) con todas tus entidades y sus relaciones. - Una tabla por entidad con campo, tipo, si admite
null, reglas y una nota justificando el tipo elegido. - 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.
- Las tres decisiones de modelado del apartado 8.3 aplicadas a tu dominio, cada una con sus alternativas descartadas y el porqué.
- Un ejemplo completo de datos ficticios en JSON con al menos 8 entidades, incluyendo un caso límite de cada regla nueva.
- 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:
- Repositorio con
main, primer commit convencional y.gitignorecorrecto (node_modules,dist,.env,coverage,cypress/videos). package.jsoncon los diez guiones del apartado 11.2, incluidoverificar.eslint.config.jscon las dos fronteras de arquitectura configuradas y comprobadas..prettierrc,jest.config.js(contestEnvironment: 'jsdom'y umbrales de cobertura) yvite.config.js.- Gancho de pre-commit con Husky y lint-staged, verificado con un commit real.
- La estructura de carpetas del apartado 12, con un
.gitkeepen las vacías. - Una prueba que demuestre que las fronteras funcionan: escribe
src/dominio/prueba-frontera.jsconimport ... from '../datos/repositorio.js', comprueba quenpm run lintfalla con tu mensaje personalizado, y bórralo. Documenta el resultado en el ADR-0001. README.mdcon la plantilla del apartado 17 (los huecos se rellenan luego).npm run verificaren 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
- ¿Qué es JavaScript?
- Configuración de tu Entorno de Desarrollo
- Tu Primer Programa en JavaScript
- Sintaxis y Conceptos Básicos de JavaScript
- Variables y Tipos de Datos
- Operadores Básicos
- Conversión de Tipos y Comparaciones
- El Proyecto del Curso: Nómada Tareas
Módulo 2: Estructuras de Control
- Sentencias Condicionales
- Bucles: for, while, do-while
- Sentencias Switch
- Control del Flujo: break, continue y Bucles Anidados
- Manejo de Errores con try-catch
Módulo 3: Funciones
- Definición y Llamada de Funciones
- Expresiones de Función y Funciones Flecha
- Parámetros y Valores de Retorno
- Ámbito y Closures
- Hoisting y el Contexto de Ejecución
- Funciones de Orden Superior
- Recursividad
Módulo 4: Objetos y Arrays
- Introducción a los Objetos
- Métodos de Objeto y la Palabra Clave
this - Arrays: Conceptos Básicos y Métodos
- Iteración sobre Arrays
- Buscar, Ordenar y Agregar Datos: find, sort y reduce
- Desestructuración de Arrays
- Desestructuración de Objetos, Spread y Rest
- JSON y Copias de Objetos
Módulo 5: Objetos y Funciones Avanzadas
- Prototipos y Herencia
- Clases y Programación Orientada a Objetos
- Encapsulación: Getters, Setters y Campos Privados
- Módulos e Importación/Exportación
- JavaScript Asíncrono: Callbacks
- Promesas y Async/Await
- El Bucle de Eventos y la Cola de Microtareas
- Iteradores y Generadores
Módulo 6: El Modelo de Objetos del Documento (DOM)
- Introducción al DOM
- Selección y Manipulación de Elementos del DOM
- Manejo de Eventos
- Propagación, Delegación y Eventos Personalizados
- Creación y Eliminación de Elementos del DOM
- Renderizado de Listas y Plantillas HTML
- Manejo y Validación de Formularios
Módulo 7: APIs del Navegador y Temas Avanzados
- Almacenamiento Local y de Sesión
- Fetch API y AJAX
- Peticiones Robustas: Errores, Timeouts y AbortController
- WebSockets
- Service Workers y Aplicaciones Web Progresivas (PWAs)
- APIs del Navegador Esenciales
- Introducción a WebAssembly
Módulo 8: Pruebas y Depuración
- Depuración de JavaScript
- Calidad de Código: ESLint, Prettier y Convenciones
- Pruebas Unitarias con Jest
- Dobles de Prueba: Mocks, Stubs y Spies
- Pruebas de Integración
- Pruebas de Extremo a Extremo con Cypress
Módulo 9: Rendimiento y Optimización
- Medir Antes de Optimizar: DevTools y Web Vitals
- Optimización del Rendimiento de JavaScript
- Gestión de Memoria
- Manipulación Eficiente del DOM
- Carga Perezosa y División de Código
Módulo 10: Frameworks y Librerías de JavaScript
- Por Qué Existen los Frameworks
- Introducción a React
- Gestión de Estado con Redux
- Conceptos Básicos de Vue.js
- Conceptos Básicos de Angular
- Elegir el Framework Adecuado
