Skip to content

Monolito

Última actualización: 06-01-2026 09:01 PM

Objetivo de este documento: describir cómo está dividido hoy el mini‑gestor de Proyectos → Grupos → Retos (pAVANTI) en términos de módulos/capas, responsabilidades, APIs internas y dependencias. Este es el monolito inicial; más adelante lo partimos en documentos específicos.

Nota de estado: el archivo conceptual pAVANTI.md menciona “sin almacenamiento automático”, pero el código actual persiste en localStorage. Este documento toma como fuente de verdad la implementación.


1) Estructura actual (carpetas y archivos)

Section titled “1) Estructura actual (carpetas y archivos)”
/src/proyectos/
components/
ProgressBar.tsx
ProyectosApp.tsx
SidebarProyectos.tsx
SidebarDerechaProyectos.tsx
TablaProyectos.tsx
hooks/
useProyectosState.ts
lib/
actividad.ts
calculoProgreso.ts
format.ts
ops.ts
types/
proyecto.ts

Lectura rápida por capas:

  • types/ → modelo de datos del dominio (sin dependencias).
  • lib/ → lógica pura de dominio (operaciones y cálculos).
  • hooks/ → estado, persistencia y “sync UX” (cambios no descargados).
  • components/ → UI: composición, interacción, rendering y estilos.

2) Mapa de dependencias (dirección permitida)

Section titled “2) Mapa de dependencias (dirección permitida)”

Regla práctica actual:

  • components/* → pueden importar de hooks/*, lib/*, types/*.
  • hooks/* → pueden importar de types/*.
  • lib/* → pueden importar de types/*.
  • types/*no importan nada interno (base del dominio).

Dependencias externas visibles:

  • uuid → generación de IDs en lib/ops.ts.
  • lucide-react → iconografía en UI.
  • structuredClone → clonación profunda para mutaciones puras.

Motivo del diseño: mantener el núcleo (types/lib) testable y sin React. La UI solo “orquesta”.


3) Módulo: types/proyecto.ts (Modelo de dominio)

Section titled “3) Módulo: types/proyecto.ts (Modelo de dominio)”
  • Proyecto

    • id, nombre, descripcion?
  • nodos: NodoGrupo[] (raíz del árbol: solo grupos)

    • creadoEn, actualizadoEn (ISO)
  • NodoBase (base común)

    • id, tipo: "grupo" | "reto", titulo, estado: "todo" | "done"
    • fecha?: string (ISO YYYY-MM-DD)
    • minutos?: number (reto: editable; grupo: derivado en UI)
    • notas?: string (solo reto)
    • orden?: number (reservado para DnD/orden estable)
  • NodoGrupo

    • tipo: "grupo"
    • hijos: NodoReto[]
    • Funciona como carpeta de organización (un proyecto puede tener muchos grupos).
  • NodoReto

    • tipo: "reto"
  • esGrupo(n): n is NodoGrupo
  • esReto(n): n is NodoReto

3.3 Invariantes (importantes para mantener)

Section titled “3.3 Invariantes (importantes para mantener)”
  • Proyecto.nodos es la única raíz del árbol.
  • Un Grupo solo contiene retos (no hay subgrupos).
  • No existen retos en raíz.
  • Un proyecto puede tener muchos grupos (organización).
  • estado es binario (no hay “in progress”).
  • orden existe pero hoy no gobierna el orden real (aún).

4) Módulo: lib/ops.ts (Operaciones puras del dominio)

Section titled “4) Módulo: lib/ops.ts (Operaciones puras del dominio)”

Responsabilidad: mutaciones “puras” sobre el estado de proyectos, siempre devolviendo una nueva lista (clonada) con timestamps actualizados.

  • nowISO() → timestamp ISO.

  • Traversal del árbol:

    • visitRaiz(nodos, fn) recorre raíz y subárbol.
    • visitGrupo(g, fn) recorre recursivamente hijos.
  • Búsqueda y manipulación:

  • findGroupById(p, id) → encuentra grupo (raíz).

    • removeNodeByIdFrom(parentArr, id) → elimina nodo en cualquier profundidad y lo retorna.

Diseño intencional: traversal y remove son internos para no exponer demasiadas primitivas aún.

  • Creación

    • crearProyecto(prev, nombre) => Proyecto[] (inserta al inicio)
    • crearGrupo(titulo, hijos?, fecha?) => NodoGrupo
    • crearReto(titulo, minutos?, fecha?) => NodoReto
  • Mutaciones del árbol

    • agregarNodoRaiz(prev, proyectoId, nodo) => Proyecto[]
    • editarNodo(prev, proyectoId, nodoId, patch) => Proyecto[]
    • toggleEstado(prev, proyectoId, nodoId) => Proyecto[]
  • moverReto(prev, proyectoId, retoId, destinoGrupoId) => Proyecto[]

    • eliminarNodo(prev, proyectoId, nodoId) => Proyecto[]
  • Proyecto

    • renombrarProyecto(prev, proyectoId, nuevoNombre) => Proyecto[]

4.3 Reglas operativas implementadas (lo que realmente pasa)

Section titled “4.3 Reglas operativas implementadas (lo que realmente pasa)”
  • Todas las mutaciones hacen structuredClone(prev) y trabajan sobre la copia.

  • Cualquier cambio que pase por ops.* refresca Proyecto.actualizadoEn.

  • editarNodo:

    • no recorta titulo durante escritura (permite teclear espacios), pero la UI hace trim() en onBlur.
    • minutos solo se aplica si el nodo es reto.
    • notas solo se aplica si el nodo es reto.
  • moverReto solo mueve retos (no grupos) y permite destino:

    • destinoGrupoId → grupo válido (raíz). No se permite mover a raíz.
  • No hay reordenamiento real: orden existe pero no se usa.


5) Módulo: lib/calculoProgreso.ts (Métricas y progreso)

Section titled “5) Módulo: lib/calculoProgreso.ts (Métricas y progreso)”

Responsabilidad: calcular progreso y contadores de manera recursiva.

  • Contadores:

    • total → cuenta grupos + retos (cada nodo cuenta 1)
    • done / pendientes
    • minutos → suma recursiva de minutos (solo retos aportan minutos; grupos “agregan” pero no suman extra)
  • sumarMinutos(nodo)

    • Si es retominutos ?? 0
    • Si es grupo → suma de sumarMinutos(hijo)
  • contarNodos(nodo)

    • Retorna contadores del subárbol
    • Regla: el grupo cuenta como unidad adicional (incrementa total en 1)
  • contadoresProyecto(proyecto)

    • Reduce contarNodos sobre proyecto.nodos
  • calcularAvancePct(proyecto)

    • Math.round(done/total * 100) con guard si total===0.
  • contadoresGrupo(g)

    • Alias para contarNodos(g) (útil para UI).
  • El “progreso” es una métrica de checklist: todo nodo vale 1.
  • Un grupo puede estar done aunque tenga hijos pendientes (hoy no hay “auto‑done”).

6) Módulo: lib/format.ts (Helpers de formato)

Section titled “6) Módulo: lib/format.ts (Helpers de formato)”
  • minsToHM(mins?) → retorna string (1h 30m, 2h, 15m).

Uso: UI del sidebar y header del proyecto.


7) Módulo: hooks/useProyectosState.ts (Estado + persistencia)

Section titled “7) Módulo: hooks/useProyectosState.ts (Estado + persistencia)”

Responsabilidad: mantener la lista de proyectos como estado React y sincronizarla con localStorage.

useProyectosState(initial?) => {

  • proyectos
  • setProyectos(fn|value) (envuelto para marcar cambios)
  • hasUnsavedChanges (cambios pendientes de descarga)
  • markSynced() (llamar tras export/descarga)
  • resetFromImported(nuevos) (llamar tras importar)

}

  • Key fija: pweb_proyectos_v1.
  • Load (una vez): en mount, lee JSON, si es array lo aplica y marca hasUnsavedChanges=true.
  • Save (siempre): cada cambio de proyectos escribe JSON al localStorage.
  • El indicador “hay cambios no descargados” no significa “no guardado” (porque ya está guardado en web), sino:

    • “hiciste cambios y aún no exportaste una copia JSON”.

Si quieres volver a la regla “sin almacenamiento automático”, este módulo sería el primer candidato a cambiar/eliminar.


8) Módulo UI: components/ProyectosApp.tsx (Orquestador)

Section titled “8) Módulo UI: components/ProyectosApp.tsx (Orquestador)”

Responsabilidad: componer la experiencia completa:

  • Hook global de proyectos (useProyectosState).
  • Selección local del proyecto (selectedId).
  • Construcción de handlers para la tabla (toggle/edit/add/delete/move) delegando a lib/ops.ts.
  • Import/Export global (JSON) y coordinación con markSynced/resetFromImported.
  • TablaProyectos recibe {proyecto, handlers}.

  • SidebarProyectos recibe:

    • lista completa, selectedId, callbacks
    • hasUnsavedChanges para el highlight del botón “Descargar”.
  • SidebarDerechaProyectos recibe { proyectos } (lista completa) porque la actividad es global (suma por día a través de todos los proyectos).

  • Exporta todos los proyectos en formato v2 (versionado) para permitir compatibilidad hacia atrás y transportar preferencias UI.
  • Archivo recomendado: pavanti-export.v2.json.
  • Luego llama markSynced().

Formato (v2):

{
"schema": "pavanti-proyectos-export",
"v": 2,
"exportedAt": "<ISO>",
"proyectos": [ ... ],
"ui": {
"v": 1,
"collapsedByProyecto": {
"<proyectoId>": ["<grupoId>", "<grupoId>"]
}
}
}

Notas:

  • ui es opcional (si no hay nada relevante que persistir, puede omitirse).
  • collapsedByProyecto guarda IDs de grupos colapsados por proyecto.
  • Acepta 2 formatos:

    • v1 (legacy): Proyecto[] (archivos antiguos sin ui).
    • v2: objeto con proyectos + ui.
  • Si viene ui, se restaura el colapsado antes de montar la tabla (para que el layout quede igual al export).

  • Luego llama resetFromImported(importedProyectos) y selecciona el primer proyecto (si existe).


9) Módulo UI: components/SidebarProyectos.tsx (Selector + acciones globales)

Section titled “9) Módulo UI: components/SidebarProyectos.tsx (Selector + acciones globales)”

Responsabilidad:

  • Ordenar proyectos por actualizadoEn descendente.

  • Renderizar cards con:

    • nombre
    • progreso %
    • ProgressBar
    • contadores done/total, pendientes
    • tiempo total (minutos del árbol)
    • “Actualizado” en local time
  • Acciones globales:

    • Importar JSON (file input oculto)
    • Descargar JSON
    • Crear nuevo proyecto
  • UX de scroll:

    • detecta overflow y muestra overlay inferior cuando no estás al final.

Detalle relevante: el botón “Descargar” resalta en ámbar si hasUnsavedChanges.


10) Módulo UI: components/SidebarDerechaProyectos.tsx (Actividad Global)

Section titled “10) Módulo UI: components/SidebarDerechaProyectos.tsx (Actividad Global)”

Este módulo ya fue separado en documentación dedicada.

Ver: Sidebar derecha — Actividad Global (Calendario) https://docs.raulnivelazo.com/proyectos/pavanti/Modularidad/sidebar-derecha/

Resumen:

  • Calendario global por meses (12 bloques), selector de año.
  • Agregación por fecha desde todos los proyectos (lib/actividad.ts).
  • Cada celda muestra horas/día (1 decimal) y color en verde degradado.
  • Auto-scroll al mes actual (o primer mes con actividad en otros años).

11) Módulo UI: components/ProgressBar.tsx (UI atómica)

Section titled “11) Módulo UI: components/ProgressBar.tsx (UI atómica)”

Responsabilidad: barra de progreso accesible.

  • Normaliza value a 0–100.
  • Usa role="progressbar" + aria-*.
  • Render solo barra (sin texto).

12) Límites actuales y riesgos (para tenerlos a la vista)

Section titled “12) Límites actuales y riesgos (para tenerlos a la vista)”
  1. Inconsistencia documental: pAVANTI.md dice “sin almacenamiento automático”, pero useProyectosState persiste en localStorage.

  2. Selección al cargar: si hay proyectos persistidos, no se auto‑selecciona uno. Es fricción menor.

  3. Mover a… solo permite mover retos entre grupos existentes (sin mover a raíz ni subgrupos).

  4. Orden (orden) está “preparado” pero no se aplica; al implementar DnD hay que decidir:

    • si el orden se aplica por orden en cada lista de hijos,
    • o si se mantiene por orden del array.
  5. structuredClone: requiere entornos modernos; en Next/React actual suele ser ok, pero si se apunta a navegadores viejos habría que reemplazarlo.


13) Split futuro: documentos que probablemente separaremos

Section titled “13) Split futuro: documentos que probablemente separaremos”

Cuando empecemos a iterar documentación por módulos, estos son candidatos claros:

  1. Modelo de dominio (types)

    • invariantes, ejemplos de JSON, migraciones de esquema.
  2. Operaciones del árbol (ops)

    • reglas de mutación, traversal, orden, DnD, edición, performance.
  3. Métricas y progreso (calculoProgreso)

    • definición formal de “progreso”, variantes posibles.
  4. Persistencia y export/import (useProyectosState)

    • decisión “localStorage vs no”, versionado de storage, validación y migración.
  5. UI: TablaProyectos

    • decisiones UX, accesibilidad, navegación teclado, popovers, colapsado.
  6. UI: SidebarProyectos / ProyectosApp

    • flujo general, selección, acciones globales, estados vacíos.
  7. Actividad por fecha (lib/actividad.ts) (YA SEPARADO)

    • ver doc: /src/content/docs/proyectos/pAVANTI/Modularidad/lib-actividad.md
  8. UI: SidebarDerechaProyectos (Actividad Global) (YA SEPARADO)

    • ver doc: /src/content/docs/proyectos/pAVANTI/Modularidad/sidebar-derecha.md

14) Checklist de coherencia (para próxima iteración)

Section titled “14) Checklist de coherencia (para próxima iteración)”
  • Alinear pAVANTI.md con la realidad: ¿persistencia automática sí/no?
  • Decidir auto‑selección del primer proyecto tras load/import.
  • Reforzar guards en ops e import: sin subgrupos y sin mover a raíz.
  • Definir estrategia de orden y preparar DnD.