# Tanemaki — Frontera `commons_core` ↔ dominio semillas *Nota de diseño (discusión). Desarrolla la Parte B de [usability-and-architecture.md](usability-and-architecture.md) y reparte las entidades de [data-model.md](data-model.md) entre el motor genérico y el dominio de semillas.* Aquí decidimos **qué vive en el motor común y qué en la app de semillas**, y con qué mecanismo se enchufan. Es la decisión que más amarra el resto, porque fija la costura que permitirá añadir mañana la "biblioteca de cosas" sin tocar Tanemaki. --- ## 1. La regla para trazar la línea Una sola pregunta decide dónde va cada cosa: > **¿La segunda app (biblioteca de cosas/herramientas) lo usaría prácticamente igual?** > - **Sí** → va en `commons_core`. > - **No, es de semillas** (especie, germinación, año de cosecha, cultivar, unidades por familia botánica) → va en el dominio `app_seeds`. > - **En la duda → déjalo en el dominio.** Subir algo al core más tarde es barato; bajarlo del core a un dominio cuando ya hay dos apps encima es caro. En una frase: **al core solo sube lo que es idéntico entre "compartir semillas" y "prestar una escalera".** --- ## 2. Qué es idéntico entre semillas y cosas (y por tanto va al core) - **Identidad** (par de claves que controlas) y firma. - **Party** (persona/colectivo con quien tratas) y **Group** (banco/colectivo). - **Confianza / reputación** (`TrustEdge`, valoraciones) — el grafo de quién confía en quién. - **Offer** (el escaparate) — anunciar lo que ofreces; ya diseñada agnóstica (lleva un *resumen* del objeto, no una FK a tablas de semillas). Serializa a NIP-99. - **Pledge** (promesa de devolución) — el Plantare generalizado. "¿Me devolviste la escalera?" == "¿devolviste semilla similar?". - **Ledger / Movement** (registro append-only de entradas/salidas) — la *mecánica* del diario es genérica. - **Transporte y sincronización** — `OfferTransport`, relays Nostr, motor CRDT, descubrimiento por geohash. **Esta es la parte difícil, y es 100% común.** ## 3. Qué es de semillas (y por tanto va al dominio) - **Species** (catálogo empaquetado Wikidata/GBIF), nombres vernáculos, cultivar. - **Germinación**, **año de cosecha**, condiciones de almacén específicas. - **Unidades por familia botánica** (mazorca, vaina, espiga, cabezuela…) y su mapeo de sugerencia. - La **UI de semillas**, sus textos i18n, sus iconos. --- ## 4. El mecanismo: cómo se enchufa el dominio al core No conviene que el core "sepa" de semillas ni que el dominio reimplemente lo difícil. Patrón elegido, en tres piezas: ### 4.1 El core define entidades base con **solo columnas universales** `Item` (identidad de algo que tienes) y `Holding` (una remesa/unidades que posees) viven en el core, pero **solo con lo común**: id, etiqueta, dueño, categoría, notas, estado de oferta, y la metadata CRDT (§4 del data-model). El core también posee `Offer`, `Pledge`, `Party`, `Group`, `TrustEdge`, `LedgerEntry`, `Identity`. ### 4.2 El dominio **extiende** con tablas laterales 1:1 (no toca las del core) Semillas añade `SeedItem(item_id → Item, species_id, cultivar, …)` y `SeedHolding(holding_id → Holding, harvest_year, germination…, plant-aware units)`. Es el patrón clásico de *tabla de extensión*: el core no importa el dominio; el dominio hace `JOIN` cuando necesita la vista rica. Encaja de perlas con Drift (DAOs separados por paquete, migraciones por paquete). *Descartado:* una bolsa JSON de atributos genéricos en `Item` (pierdes tipado, consultas y migraciones). Quizá para extras que casi nunca se consultan, no como mecanismo principal. ### 4.3 El core habla con el dominio por **contratos**, no por conocimiento - `Offer` referencia lo ofrecido por un **resumen desnormalizado** (`variety_summary`) + un id local opaco, no por FK a `SeedItem`. Así el core publica y sincroniza ofertas **sin entender de semillas**. *(Ya lo dejamos así en el modelo — la costura estaba bien encaminada.)* - `Pledge` referencia partes + un texto de lo debido, no tablas de dominio. - `LedgerEntry` apunta a su sujeto por un puntero opaco `(tabla_dominio, id)`; la mecánica append-only/CRDT es del core. Dirección de dependencia, inviolable: **`app_seeds` depende de `commons_core`. Nunca al revés.** Si algún día el core necesita algo del dominio, es señal de que ese algo estaba mal colocado. --- ## 5. Dos generalizaciones que conviene fijar ya (son baratas y evitan migrar) - **`Pledge.return_kind`** ∈ `{ similar, same_item }`. Semillas usan `similar` (devuelves *algo parecido*); cosas usan `same_item` (devuelves *ese* objeto). **Un solo enum captura toda la diferencia** entre el Plantare y el préstamo de una herramienta. - **`Offer.offer_type`** incluye ya `lend` (devolver el mismo objeto) junto a `gift/exchange/sale/wanted`. Aditivo y barato; en semillas casi no se usa, pero deja la puerta lista. - **Cantidad genérica**: el core define un tipo `Quantity { kind, amount?, label? }`; el *vocabulario* de `kind` (packet/handful/grams para todos; cob/pod/ear para semillas) y la sugerencia por familia **los pone el dominio**. Así "1 unidad / una caja" (cosas) y "una mazorca / una vaina" (semillas) usan la misma forma. --- ## 6. Estructura de repositorio (refinada) ``` tane/ (repo, hogar: ~/dev/tane) packages/ commons_core/ ← identidad, Party, Group, TrustEdge, Offer, Pledge, LedgerEntry, Item/Holding base, transporte+CRDT+sync, discovery (geohash). SIN nada de semillas. commons_ui/ (opcional) ← kit de widgets accesibles (targets grandes, foto-first, icono+palabra) reutilizable por futuras apps. apps/ app_seeds/ ← Tanemaki: Species, SeedItem/SeedHolding, germinación, cosecha, unidades por familia, UI e i18n de semillas. ``` Drift: tablas del core en `commons_core`, tablas de extensión en `app_seeds`, migraciones versionadas **por paquete** (§5 del data-model). El día que exista `app_things`, reusa `commons_core` (y `commons_ui`) sin tocar `app_seeds`. --- ## 7. Pragmatismo: no sobre-diseñar el motor ahora - **Construye Tanemaki concreto primero.** Mete en `commons_core` solo lo *ya conocido-compartido* (identidad, Party, Offer, Pledge, trust, sync, discovery). Para `Item/Holding`, empieza con tabla base del core + tabla de extensión de semillas; si al llegar la app de cosas resulta incómodo, **refactorizar dentro del monorepo es barato** (todo el código está junto y se mueve de un tirón). - No inventes interfaces genéricas "por si acaso" para partes cuya forma real solo conocerás cuando exista la segunda app. La regla del §1 ("en la duda, al dominio") protege de la abstracción prematura. --- ## 8. Decidido - **Nombre del paquete núcleo:** **`commons_core`** (inglés, encaja con la convención de código y con la org Comunes). - **`commons_ui`:** nombre e idea fijados, pero **arranca dentro de `app_seeds`**; se extrae a paquete cuando llegue la segunda app (evita andamiaje prematuro, §7). - **`Item` / `Holding`:** arranque **conservador** — el core NO lleva tablas base `Item/Holding` todavía. `Variety/Lot` viven **enteras en el dominio de semillas**. El core arranca solo con lo seguro-compartido (`Offer`, `Pledge`, `Party`, `Group`, `TrustEdge`, `Identity`, transporte+CRDT+sync, discovery). Cuando exista la app de cosas y revele la forma común, se extrae `Item/Holding` al core (refactor barato dentro del monorepo). - **Monorepo:** **`pub workspaces`** (nativo de Dart, sin dependencias extra). Descartado `melos`. ### Estructura de arranque resultante ``` tane/ pubspec.yaml ← workspace raíz (pub workspaces) packages/ commons_core/ ← Offer, Pledge, Party, Group, TrustEdge, Identity, transport (OfferTransport/Nostr), CRDT+sync, geohash. (Item/Holding se añadirán aquí al llegar la 2ª app.) apps/ app_seeds/ ← Tanemaki: Variety, Lot, Movement, Species, germinación, cosecha, unidades por familia, UI e i18n, commons_ui embebido. ``` Dependencia: `app_seeds → commons_core`, nunca al revés. Migraciones Drift versionadas por paquete.