================================================================================
                      OASIS · MAPA DE FUNCIONES Y FLUJO
        Cómo se relacionan las funciones lógicas entre archivos,
                     la persistencia (SSB) y la media (blobs)
                              versión 0.8.1
================================================================================

LEYENDA
  [proc]  proceso             ( )  función / método
  -->     llamada / flujo     ===  límite de proceso        ~~~  red / disco
  *.js    archivo             #NNN número de línea aprox.


================================================================================
 0. VISIÓN GLOBAL: DOS PROCESOS
================================================================================

   NAVEGADOR                                          OTROS PEERS SSB
   (HTML puro,                                       (misma SHS cap)
    sin JS)                                                 |
      |  HTTP :3000                                         | net:IP:8008~shs
      v                                                     v   ~~~~~~~~~~~~~
  ============================[proc gui]==      ==============[proc sbot]======
  | src/backend/backend.js  (Koa router) |     | src/server/SSB_server.js    |
  |                                       |     |  SecretStack({caps}).use(..)|
  |  cooler = src/client/gui.js           |     |   ssb-db (log append-only)  |
  |        |                              | <-> |   ssb-ebt / ssb-friends     |
  |        | cooler.open()                |unix |   ssb-blobs / ssb-conn      |
  |        v                              |sock |   ssb-box / ssb-private     |
  |   ssb (cliente muxrpc) ---------------|~noauth-> muxrpc API (publish,get, |
  |                                       |     |        createLogStream,...)  |
  =========================================     ===============================
        modelos + vistas                            replicación P2P por gossip


================================================================================
 1. CADENA DE UNA PETICIÓN (el patrón de TODO módulo)
================================================================================

  navegador
     | GET /modulo  (o POST /modulo/accion)
     v
  middleware.js (#43..#209) ── app.listen({host,port})
     | setLanguage(cookie)  ·  gate indexación  ·  refresco shared-state(60s)
     v
  backend.js  router.get('/modulo')              #1270..#7124  (~472 rutas)
     | checkMod(ctx,'moduloMod')  #170 ── off? --> redirect /modules
     |
     +--> xModel.metodo(filtro)        [ LECTURA / ESCRITURA SSB ]  -----+
     |        (src/models/xxx_model.js)                                  |
     |                                                                   v
     |    <------------------ datos JS ------------------------  cooler.open()
     |                                                              (gui.js)
     +--> xView(datos, filtro)         [ RENDER HTML ]                   |
     |        (src/views/xxx_view.js)                                    v
     |        template(i18n.titulo, section(...))                   ssb muxrpc
     v
  ctx.body = "<html>...</html>"  --> Koa --> navegador

  POST: koaBody() -> stripDangerousTags() -> xModel.crear() -> ctx.redirect()
        (patrón Post/Redirect/Get; el GET siguiente re-renderiza)


================================================================================
 2. PERSISTENCIA: cómo el MODELO habla con SSB (pull-stream + publish)
================================================================================

  src/models/xxx_model.js  =  module.exports = ({cooler, ...deps}) => ({...})

  openSsb() ............ if(!ssb) ssb = await cooler.open();  // lazy, cacheado
       |
       v
  ESCRIBIR (persistir):
       ssb.publish({ type:'<tipo>', ...campos }, cb)
            |                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            +--> ssb-db: añade mensaje FIRMADO (ed25519) al log append-only
                 |        del feed propio  @miclave.ed25519
                 +--> ssb-ebt replica el mensaje a los peers (hops<=2)  ~~~~>

  LEER (consultar):
       pull(
         ssb.createLogStream({limit})      <-- lee el log local (todos los feeds
         pull.filter(m => m.value.content.type === '<tipo>')   replicados)
         pull.collect((err,res)=>...)
       )
       (variantes: ssb.messagesByType, ssb.query.read, ssb.backlinks.read,
        ssb.createUserStream{id})

  BORRAR (lógico; el log NO se borra):
       ssb.publish({ type:'tombstone', target:<msgId>, author })
            |
            +--> tombstone_validator.js :: buildValidatedTombstoneSet(msgs)
                 acepta SOLO si author(tombstone) === author(target)   [autoría]

  EDITAR:
       ssb.publish({ ...nuevo, replaces:<idAnterior> })  + tombstone del viejo
            |
            +--> al leer, se sigue la cadena replaces -> hasta la "punta"

  ----------------------------------------------------------------------------
  TIPOS DE MENSAJE (content.type)   = el discriminador en el log compartido
  ----------------------------------------------------------------------------
  nativos SSB : post · vote(like) · contact(follow/block) · about(perfil)
  Oasis       : votes · tombstone · task · event · calendar · transfer ·
                tribe · market · report · job · project · gameScore · ...


================================================================================
 3. MEDIA: cómo viajan los ficheros (BLOBS, fuera del log)
================================================================================

  El log solo guarda el HASH.  El binario viaja aparte por la red SSB.

  SUBIR:
    navegador --(multipart)--> backend.js  --> blobHandler.js
        ssb.blobs.add(pull-stream)  --> &<sha256>.sha256   (ref = hash)
        (luego se publica un mensaje con ese ref dentro de content)

  MOSTRAR / DESCARGAR:
    vista genera <img src="/blob/&xxx.sha256">  ó  markdown.js resuelve menciones
        |
        v
    backend.js ruta /blob/:id --> blobHandler.js
        ssb.blobs.has(id)? --- no ---> ssb.blobs.want(id)  ~~~~~~~~~~~~~~~~~>
        |                                  (gossip "want/has" a los peers)
        +-- si ---> ssb.blobs.get(id) -> pull-stream -> ctx.body (bytes)

    límite: 50 MB (ssb_config.js #17-19)


================================================================================
 4. RENDER: cómo la VISTA construye HTML (hyperaxe, sin .html)
================================================================================

  src/views/xxx_view.js
     require("../server/node_modules/hyperaxe")  -> div(), form(), section()...
     require('./main_views')  -> { template, i18n, userLink, markdown, chips }

  exports.xView(datos) =
     template( i18n.xTitle,                         // <- LAYOUT global
        section( h2(i18n.xTitle), ...lista... ) )
                 |
                 v
  main_views.js :: template(titulo, ...elems)  #1017..#1321
     html
      +- head: <title> · CSS · /assets/themes/<tema>.css   (oasis-config.themes)
      +- body
          +- header (logo, inbox badge <- shared-state, publish, search)
          +- sidebar-left  =  navGroup(...) { renderXxxLink() por módulo }
          |        navLink({href,emoji,text})  #327
          |        renderGamesLink() #898 -> si gamesMod==='on'  (config-manager)
          +- main-content  =  tus secciones

  i18n:  objeto compartido mutado por setLanguage(lang)  (11 idiomas)
         backend middleware lo fija por cookie en cada request
  markdown.js:  ssb-markdown -> resuelve @feed,%msg,&blob,#hashtag a rutas /...


================================================================================
 5. GRAFO DE DEPENDENCIAS ENTRE ARCHIVOS
================================================================================

  src/backend/backend.js  (orquestador central)
    |--require--> ../client/gui.js .................. cooler (conexión SSB)
    |--require--> ../client/middleware.js ........... http server Koa
    |--require--> ../configs/config-manager.js ...... getConfig/saveConfig
    |--require--> ../configs/shared-state.js ........ estado en memoria
    |--require--> ./sanitizeHtml.js / ./blobHandler.js
    |--require--> ../server/SSB_server.js ........... config + (sbot in-process)
    |--require--> ../models/*_model.js  (factories: ({cooler,deps}))
    |                 |--inyecta--> otros modelos (agenda<-tasks,events,market..)
    |                 |--inyecta--> *Crypto (crypto.js por dominio)
    |--require--> ../views/*_view.js   (funciones de render)

  src/models/xxx_model.js
    |--require--> pull-stream · moment
    |--require--> ./tombstone_validator.js   (función pura, sin deps)
    |--require--> ../configs/config-manager.js
    |--recibe----> cooler (NO importa gui directamente)

  src/views/xxx_view.js
    |--require--> hyperaxe
    |--require--> ./main_views.js  { template, i18n, userLink, helpers }
    |--require--> moment · (a veces) ../server/SSB_server (feedId propio)

  src/views/main_views.js  (el "framework" de vistas)
    |--require--> hyperaxe · lodash · highlight.js · qrcode · moment
    |--require--> ./markdown.js · ../backend/renderUrl.js · ./sanitizeHtml?
    |--require--> ../backend/nameCache.js  (Map nombres <- main_models warmup)
    |--require--> ../client/gui.js · ../configs/{config-manager,shared-state}
    |--require--> ../client/assets/translations/i18n.js  (11 idiomas)

  src/server/SSB_server.js  (sbot)
    |--require--> secret-stack · ssb-caps · ssb-db · plugins ssb-*
    |--require--> ./ssb_config.js (<- server-config.json) · ./ssb_metadata.js
    |--require--> ./lanRouter.js  (descubrimiento LAN)


================================================================================
 6. EL MÓDULO GAMES, DE PRINCIPIO A FIN (ejemplo concreto)
================================================================================

  MENU            main_views.js #898 renderGamesLink() --(gamesMod==on)--> /games
                                                                              |
  LOBBY           backend.js #1358 GET /games                                 |
                    checkMod #170 -> gamesModel.getHallOfFame() #40 ----------+
                                       |  pull(createLogStream)               |
                                       |  filter type==='gameScore'           |
                                       |  best[game:author]=max(score)        |
                                    gamesView(filter,hall) #95 -> HTML        |
                                                                              |
  PLAY            GET /games/:name #1364 -> gameShellView(name) #60           |
                    <iframe src="/game-assets/<name>/index.html">             |
                              |                                               |
  STATIC          middleware.js #109 mount('/game-assets', src/games/) ~~disk~+
                              |
  JUEGO           src/games/<name>/index.html  (HTML+CSS+JS inline, canvas)
                    al terminar: form POST -> /games/submit-score target=_top
                              |
  SCORE           POST /games/submit-score #1369
                    gamesModel.submitScore(game,score) #28
                       ssb.publish({type:'gameScore',game,score}) ~~> SSB log
                    redirect /games?filter=scoring -> Hall of Fame

  LISTAS A SINCRONIZAR (sin registro central):
    games_view.js getGames() #5  +  VALID_GAME_IDS #58   (16 juegos)
    games_model.js VALID_GAMES #5                         (14 -> falta rps,audio)
    backend.js listas módulos #1346 #7038 #7054 #7040     ('games')
    oasis-config.json modules.gamesMod
    modules_view.js array modules #23
    oasis_*.js  gamesTitle, games<Nombre>Title/Desc  (x11 idiomas)


================================================================================
 7. CONFIG Y ESTADO (puntos de coordinación transversal)
================================================================================

  oasis-config.json  <--getConfig()/saveConfig()-->  config-manager.js
     |  modules.{xMod:on/off}  themes.current  ux.current  language
     |  wish  pmVisibility  lanBroadcasting  ssbLogStream.limit  wallet
     +--leen--> backend.js (checkMod) · main_views (render links/tema) · modelos

  server-config.json  <-- ssb_config.js -->  SSB_server.js
     |  caps.shs (red privada)  friends.hops=2  connections (puerto 8008, unix)

  shared-state.js  (memoria, NO persistente)
     |  inboxCount  carbon  peersOnline  ecoValue  lastRefresh
     +--escribe--> middleware refresco 60s     +--lee--> main_views (badges)

  nameCache.js (Map en memoria)
     +--alimenta--> main_models._startNameWarmup (escucha 'about' en live)
     +--consume---> main_models.about · main_views (mostrar nombres)


================================================================================
 RESUMEN EN UNA FRASE
================================================================================
  backend.js enruta -> el MODELO persiste/lee mensajes tipados en el log SSB
  (replicado P2P por hops) y los blobs por want/has -> la VISTA (hyperaxe)
  los pinta dentro de template() con i18n y tema -> y todo módulo se activa con
  un flag xMod en oasis-config.json comprobado por checkMod().
================================================================================
