gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

feat(metal): el censo pasa a la versión 2 — se puede DIBUJAR el terreno, y el sector de 512 estaba horneado

churay pidió (SDD-COORDINACION-TAKANA.md, en tawasuyu) lo que le falta para dibujar los discos en
vez de listarlos. Concedidos §2.1–§2.4, sin mover ni una decisión a la cara: veredicto y usable
siguen siendo los únicos que deciden.

Por fila: clase (particion·hueco·disco), disco, inicio_bytes, tamano_bytes, tipo_guid. Y un array
discos con modelo, removible, tamano_bytes, sector_bytes, tabla — en array propio y no repetido en
cada fila, porque un dato del disco copiado por partición se puede contradecir entre filas.

Un hueco NO tiene dev propio: ya traía el del DISCO, así que dos huecos del mismo disco llegaban
con el mismo dev y quien los indexe por ahí pierde uno sin que nada falle. Su identidad es
disco+inicio_bytes, y ahora está dicho.

Y apareció un defecto que el pedido no buscaba: mib_de_sectores dividía por 2048 a ciegas. sfdisk
cuenta en sectores LÓGICOS (4096 en un 4Kn) y /sys/class/block/*/size cuenta SIEMPRE en 512. Medido
con losetup --sector-size 4096, viejo contra nuevo sobre el mismo disco: una partición de 1 GiB
salía 128 M, y el hueco de 7166 M salía 895 M y se rechazaba por chico. O sea que en una máquina
4Kn el censo ESCONDÍA el único sitio donde instalar no destruye nada.

Probado en el anfitrión con discos de verdad y con loops de 512 y de 4096: JSON válido, sólo por
stdout, huecos con su inicio, y el disco sin tabla como clase propia.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

f06d943f9365bd17dd8db1b79f17862e559a8ff8 · ruwaq Sergio · (26 p'unchaw ñawpaq) · tayta 2a0175eb3 · kay commitpi sach'ata qhaway

Semántico sayk'uy

Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.

3 qillqakuna · +200 −15

diff --git a/crates/takana-cli/src/metal.rs b/crates/takana-cli/src/metal.rs
index 1a95ccaf..72568754 100644
--- a/crates/takana-cli/src/metal.rs
+++ b/crates/takana-cli/src/metal.rs
@@ -26,6 +26,26 @@
 //! (churay). El riesgo que esto evita está medido en otro frente: dos censos divergen, y el que se
 //! equivoca no muestra de menos — formatea de más.
 //!
+//! ── EL CONTRATO, VERSIÓN 2 (2026-09-25) ───────────────────────────────────────────────────────
+//! La v1 alcanzaba para una LISTA y no para un DIBUJO: sin saber a qué disco pertenece cada fila ni
+//! en qué byte empieza, no se sabe qué va al lado de qué ni qué hueco queda entre dos. La v2 agrega
+//! lo que hace falta para pintar el terreno, y **ni un campo de decisión**: `veredicto` y `usable`
+//! siguen siendo los únicos que deciden, y siguen saliendo de acá.
+//!
+//! Por fila: `clase` (`particion` · `hueco` · `disco`), `disco`, `inicio_bytes`, `tamano_bytes`,
+//! `tipo_guid`. Y un array `discos` con el APARATO: `modelo`, `removible`, `tamano_bytes`,
+//! `sector_bytes`, `tabla`.
+//!
+//! ⚠ **`tamano_bytes` existe además de `mib` porque `mib` está redondeado**: una barra proporcional
+//! construida con MiB acumula error a lo largo del disco y el hueco final sale corrido.
+//!
+//! ⚠ **Un `hueco` NO tiene `dev` propio**: su `dev` es el del disco, y dos huecos del mismo disco
+//! traen el MISMO `dev`. Su identidad es `disco` + `inicio_bytes`. Una cara que los indexe por
+//! `dev` se queda con uno solo y pierde el otro sin que nada falle.
+//!
+//! ⚠ El aparato va en un array propio y **no repetido en cada fila**: un dato del disco copiado en
+//! cada partición se puede contradecir entre filas, y entonces hay que decidir a cuál creerle.
+//!
 //! ── LO QUE ESTE VERBO NO HACE ─────────────────────────────────────────────────────────────────
 //! No elige partición, no formatea y no escribe la NVRAM. Todo eso pasa DESPUÉS del salto, dentro
 //! del instalador, que es donde el disco destino puede estar desmontado. Acá sólo se comprueba, se
diff --git a/docs/adr/0021-dueno-del-verbo-de-instalar.md b/docs/adr/0021-dueno-del-verbo-de-instalar.md
index 1107ef60..a50ac7c5 100644
--- a/docs/adr/0021-dueno-del-verbo-de-instalar.md
+++ b/docs/adr/0021-dueno-del-verbo-de-instalar.md
@@ -237,3 +237,80 @@ equivocan del lado seguro y están en sus tests:
 | un veredicto **desconocido** no se ofrece, aunque venga `usable: true` | si takana publica uno nuevo y ese churay es viejo: perder una opción se nota; ofrecer la equivocada, no |
 | un JSON que no parsea es un **error**, no un censo vacío | un censo vacío se pinta como «esta máquina no tiene discos», que es la mentira tranquila |
 | lo que **no** se puede elegir se muestra igual, con su motivo | una lista que sólo trae lo elegible se lee como «acá no hay nada más», y el que no encuentra su partición no sabe si el instalador no la vio o decidió no ofrecerla |
+
+## Respuesta a la propuesta de churay (2026-09-25)
+
+Del otro lado se escribió `02_ruway/churay/SDD-COORDINACION-TAKANA.md`: una lista de **datos** que la
+cara necesita para dibujar el terreno en vez de listarlo, explícitamente sin mover ni una decisión a
+churay. Se contesta acá porque dos de sus puntos son decisiones y no código.
+
+### Concedido e implementado: el censo pasa a la versión 2
+
+Los cuatro puntos de §2.1–§2.4 del pedido, en `censo()` y `censo_json()`. Por fila: `clase`
+(`particion` · `hueco` · `disco`), `disco`, `inicio_bytes`, `tamano_bytes`, `tipo_guid`. Y un array
+`discos` con `modelo`, `removible`, `tamano_bytes`, `sector_bytes`, `tabla`.
+
+**Ni un campo de decisión.** `veredicto` y `usable` siguen siendo los únicos que deciden y siguen
+saliendo de acá; lo agregado es para pintar y explicar, que es exactamente lo que se pidió.
+
+Dos cosas salieron distintas de lo propuesto, y conviene saber por qué:
+
+1. **El aparato va en un array propio, no como campos de cada fila.** Se pidió `disco_modelo` /
+   `disco_removible` / `disco_tamano_bytes` por partición. Un dato del disco repetido en cada fila se
+   puede **contradecir entre filas** —y entonces hay que decidir a cuál creerle—, así que cada fila
+   trae `disco` y eso es la clave del array. Es un `HashMap` del lado de la cara y un error menos.
+2. **El hueco ya venía con `dev`, y eso era peor que no tenerlo.** El pedido dice que un hueco «no
+   tiene `dev` ni `partuuid`»; en realidad **ya traía `dev` = el disco**, así que dos huecos del
+   mismo disco llegaban con el MISMO `dev` y una cara que los indexe por ahí se queda con uno y
+   pierde el otro **sin que nada falle**. Por eso existe `clase`: la identidad de un hueco es
+   `disco` + `inicio_bytes`, y está dicho en el contrato en vez de quedar implícito.
+
+### Y apareció un defecto que el pedido no buscaba: el sector de 512 estaba horneado
+
+`mib_de_sectores()` dividía por 2048 a ciegas. `sfdisk -d` cuenta en sectores **lógicos**, que en un
+disco 4Kn son 4096 — y `/sys/class/block/<n>/size`, que usa la otra rama, cuenta **siempre** en
+unidades de 512. Las dos conversiones estaban mezcladas en una.
+
+Medido con un loop de sector 4096 (`losetup --sector-size 4096`), censo viejo contra censo nuevo
+sobre el MISMO disco:
+
+| | viejo | nuevo | real |
+|---|---|---|---|
+| partición de 1 GiB | **128 M** | 1024 M | 1024 M |
+| el hueco de al lado | **895 M → ✗ NO TOCAR, «hacen falta 2048 M»** | 7166 M → ✓ instalar al lado | 7166 M |
+
+O sea que en una máquina con discos 4Kn el censo **escondía el único sitio donde instalar no destruye
+nada** y decía que era chico. Se equivocaba del lado seguro —ocultaba, no ofrecía— pero el resultado
+práctico es un instalador que en esa máquina contesta «acá no hay dónde». Es el mismo patrón del
+`/dev/sda1` de 2 MiB que este ADR ya trae: **el veredicto mira lo que hay y se olvida de cuánto
+cabe**, y la unidad en la que se mide es parte de «cuánto».
+
+Comprobado además que el JSON sale **solo** por stdout (la línea de la herramienta resuelta del store
+va por stderr) y que parsea; los huecos, el 4Kn y el disco sin tabla, con discos de verdad.
+
+### Lo que NO se concede todavía, y qué falta para poder
+
+- **§2.5 `usado_bytes`** — de acuerdo con el orden propuesto: es el más caro (montar en sólo lectura
+  para medir) y el que menos cambia una decisión. Queda anotado con su condición, que es del pedido y
+  es correcta: **si no hay dato, ausente y no cero.** Un 0 % dibujado sobre una partición llena
+  miente justo en el número que hace decidir.
+- **§3 conservar `/home`** — `takana metal install --conservar PARTUUID=…:/home` es aceptable como
+  forma, y las tres condiciones que el pedido pone (no formatear, comprobar que monta ANTES de tocar
+  la raíz, declararlo en el plan) son de takana y se asumen. Pero **la trampa 1 no es de la cara y no
+  está decidida**: si el usuario que crea takana no tiene el mismo `uid` que el del sistema viejo,
+  los archivos quedan de otro dueño y el escritorio arranca sin poder escribir en su propia carpeta.
+  Las dos salidas —crear el usuario con el uid que se encuentra, o cambiar dueños y decirlo— son
+  decisiones de takana con consecuencias distintas, y hasta que se elija **el botón no se ofrece**.
+  La instantánea de `pacha-dotfiles` que churay propone para la trampa 2 no le pide nada a takana y
+  no hace falta esperarla.
+- **§7 la raíz del suelo** — hay dos llaves reales y un anillo firmado que hoy **no anclan nada**, y
+  eso es de este repo decidirlo. No están apagadas por diseño: están sin conectar, que se ve igual y
+  no protege lo mismo. Sigue abierto quién es dueño de esa raíz; una llave sin dueño es peor que no
+  tenerla.
+
+### Una corrección menor al pedido
+
+Su §1 lista los veredictos como `CANDIDATA · LIBRE · RAIZ_VIVA · SOLO_ESP · EN_USO · NO_TOCAR` y
+falta **`CRUDA`** (sin identificar, usable con confirmación), que el censo emite desde el principio.
+No es un problema real —`churay-core::metal` sí lo conoce y lo trata—, pero la tabla del documento
+quedó corta y es la que alguien va a leer.
diff --git a/scripts/takana-install-partition.sh b/scripts/takana-install-partition.sh
index 55ee12e4..2a43a7ce 100755
--- a/scripts/takana-install-partition.sh
+++ b/scripts/takana-install-partition.sh
@@ -34,6 +34,14 @@
 #                              PARTUUID de cada partición y qué se puede usar. Empezá por acá.
 #   --json                     (con --survey) el mismo censo en JSON. Es el CONTRATO que consume
 #                              una cara gráfica: `takana metal survey --json` lo envuelve.
+#                              **Versión 2** desde el 2026-09-25: cada fila trae además `clase`,
+#                              `disco`, `inicio_bytes`, `tamano_bytes` y `tipo_guid`, y hay un array
+#                              `discos` con el aparato. Es lo que hace falta para DIBUJAR el terreno
+#                              en vez de listarlo; nada de eso decide — `veredicto` y `usable`
+#                              siguen siendo los únicos que deciden.
+#                              ⚠ El JSON sale por stdout y SOLO el JSON: la línea de qué herramienta
+#                              se resolvió del store va por stderr. Si algún día se cuela en stdout,
+#                              la cara deja de parsear y el censo se lee como «no hay discos».
 #   --pick <fichero>           mostrar el censo, DEJAR ELEGIR y NO TOCAR NADA: deja la elección en
 #                              <fichero> (`DEST=…`, `FREE=1`, `DISK=…`) y sale. Es lo que permite
 #                              decidir ANTES del salto — así lo de después no necesita pantalla.
@@ -245,6 +253,24 @@ discos_del_sistema() {
 }
 
 mib_de_sectores() { echo $(( ${1:-0} / 2048 )); }
+mib_de_bytes()    { echo $(( ${1:-0} / 1048576 )); }
+
+# ── sector_bytes_de <disco> — el sector LÓGICO, que no siempre es 512 ──────────────────────────
+# `sfdisk -d` cuenta en sectores LÓGICOS y lo declara en su cabecera (`sector-size: 512`). En un
+# disco 4Kn eso son 4096, y dividir por 2048 a ciegas da un tamaño **ocho veces menor**: una
+# partición de 40 G se muestra como 5 G y el piso de `mib_necesarios` la rechaza por chica — o sea
+# que el error no se queda en la pantalla, **decide**.
+#
+# ⚠ Y no se puede unificar con `/sys/class/block/<n>/size`, que es lo que usa la rama del disco sin
+# tabla: **ése siempre cuenta en unidades de 512**, por convención del kernel, incluso en 4Kn.
+# Mezclar las dos conversiones es el error que esta separación evita.
+sector_bytes_de() {
+    _sb_v=$("$SFDISK" -d "$1" 2>/dev/null | sed -n 's/^sector-size:[ ]*\([0-9]*\).*/\1/p' | head -1)
+    case "${_sb_v:-}" in ''|*[!0-9]*) echo 512 ;; *) echo "$_sb_v" ;; esac
+}
+
+# ── bytes_del_disco <disco> — el tamaño del APARATO, en bytes ──────────────────────────────────
+bytes_del_disco() { echo $(( $(cat "/sys/class/block/$(nombre_kernel "$1")/size" 2>/dev/null || echo 0) * 512 )); }
 
 # ── mib_minimos — el piso, calculado UNA vez ──────────────────────────────────────────────────
 # `mib_necesarios` mide el árbol y no es gratis (`du -sm` sobre el rootfs); el censo la llamaría una
@@ -327,11 +353,24 @@ clasificar() {
     echo "CANDIDATA|$_cl_q"
 }
 
-# censo [--solo-candidatas] → una línea por partición:
-#   <dev>|<partuuid>|<mib>|<fstype>|<label>|<montaje>|<VEREDICTO>|<explicación>
+# censo [--solo-candidatas] → una línea por partición, hueco o disco sin tabla:
+#   <dev>|<partuuid>|<mib>|<fstype>|<label>|<montaje>|<VEREDICTO>|
+#   <clase>|<disco>|<inicio_bytes>|<tamano_bytes>|<tipo_guid>|<explicación>
+#
+# ⚠ **La explicación va ÚLTIMA a propósito**: es el único campo de texto libre, y con `IFS='|'` el
+# último `read` se come lo que sobre. Un campo nuevo va ANTES de ella, y hay que tocar los tres
+# lectores (`mostrar_censo`, `censo_json`, `elegir_destino`) — no hay cuarto.
+#
+# `clase` distingue las tres cosas que no son lo mismo y que hasta hoy se veían iguales:
+#   `particion`  una entrada de la tabla
+#   `hueco`      espacio sin asignar — **no tiene `dev` propio**: `dev` es el DISCO, y dos huecos
+#                del mismo disco traen el mismo `dev`. La identidad de un hueco es
+#                `disco` + `inicio_bytes`, y nada más.
+#   `disco`      un disco SIN tabla de particiones (formateado directo, o crudo)
 censo() {
     _ce_solo="${1:-}"
     for _ce_d in $(discos_del_sistema); do
+        _ce_sb=$(sector_bytes_de "$_ce_d")
         # ⚠ Un disco SIN TABLA no es un disco vacío: puede estar formateado directo y montado —así
         # vive el store de este hub, `/dev/sdb` con un ext4 encima y nada más—. Sin esta rama el
         # censo lo OMITE por completo, que es la peor forma de presentar algo que existe y está en
@@ -345,12 +384,13 @@ censo() {
             _ce_sz=$(( $(cat "/sys/class/block/$(nombre_kernel "$_ce_d")/size" 2>/dev/null || echo 0) ))
             if [ -n "$_ce_mn" ]; then _ce_v="EN_USO"; _ce_e="disco SIN TABLA de particiones, montado en $_ce_mn"
             else _ce_v="NO_TOCAR"; _ce_e="disco SIN TABLA de particiones (${_ce_fs:-sin filesystem})"; fi
-            echo "$_ce_d|-|$(mib_de_sectores "$_ce_sz")|${_ce_fs}|${_ce_lb}|${_ce_mn}|$_ce_v|$_ce_e"
+            echo "$_ce_d|-|$(mib_de_sectores "$_ce_sz")|${_ce_fs}|${_ce_lb}|${_ce_mn}|$_ce_v|disco|$_ce_d|0|$(( _ce_sz * 512 ))|-|$_ce_e"
             continue
         fi
         "$SFDISK" -d "$_ce_d" 2>/dev/null | grep "^${_ce_d}" | while IFS= read -r _ce_l; do
             _ce_dev=$(printf '%s' "$_ce_l" | awk '{print $1}')
             _ce_sz=$(printf '%s'  "$_ce_l" | sed -n 's/.*size=[ ]*\([0-9]*\).*/\1/p')
+            _ce_st=$(printf '%s'  "$_ce_l" | sed -n 's/.*start=[ ]*\([0-9]*\).*/\1/p')
             _ce_ty=$(printf '%s'  "$_ce_l" | sed -n 's/.*type=\([0-9A-Fa-f-]*\).*/\1/p')
             _ce_uu=$(printf '%s'  "$_ce_l" | sed -n 's/.*uuid=\([0-9A-Fa-f-]*\).*/\1/p')
             _ce_bl=$(blkid "$_ce_dev" 2>/dev/null || true)
@@ -359,16 +399,23 @@ censo() {
             _ce_mn=$(awk -v d="$_ce_dev" '$1==d {print $2; exit}' /proc/mounts 2>/dev/null || true)
             # La raíz viva se decide por número de dispositivo, no por el nombre de /proc/mounts.
             if es_la_raiz_viva "$_ce_dev"; then _ce_mn="/"; fi
-            _ce_cl=$(clasificar "$_ce_ty" "$_ce_fs" "$_ce_mn" "$(mib_de_sectores "$_ce_sz")")
+            # En bytes y con el sector del disco, no con un 2048 horneado: en 4Kn el tamaño sale
+            # ocho veces menor y `clasificar` rechaza por chica una partición que sobra.
+            _ce_by=$(( _ce_sz * _ce_sb ))
+            _ce_ib=$(( ${_ce_st:-0} * _ce_sb ))
+            _ce_cl=$(clasificar "$_ce_ty" "$_ce_fs" "$_ce_mn" "$(mib_de_bytes "$_ce_by")")
             _ce_ver="${_ce_cl%%|*}"; _ce_exp="${_ce_cl#*|}"
             [ "$_ce_solo" = "--solo-candidatas" ] && [ "$_ce_ver" != "CANDIDATA" ] && continue
-            echo "$_ce_dev|$_ce_uu|$(mib_de_sectores "$_ce_sz")|$_ce_fs|$_ce_lb|$_ce_mn|$_ce_ver|$_ce_exp"
+            echo "$_ce_dev|$_ce_uu|$(mib_de_bytes "$_ce_by")|$_ce_fs|$_ce_lb|$_ce_mn|$_ce_ver|particion|$_ce_d|$_ce_ib|$_ce_by|${_ce_ty:--}|$_ce_exp"
         done
         # El espacio SIN ASIGNAR es un candidato más, y el único que no destruye nada.
-        "$SFDISK" -F "$_ce_d" 2>/dev/null | awk -v d="$_ce_d" -v min="$(mib_minimos)" \
-            'NF==4 && $1 ~ /^[0-9]+$/ { m=int($3/2048);
-               if (m >= min) print d"|-|"m"|-|-|-|LIBRE|espacio sin asignar en "d;
-               else print d"|-|"m"|-|-|-|NO_TOCAR|hueco sin asignar en "d", pero son "m" M y hacen falta al menos "min" M" }'
+        # ⚠ Las columnas de `sfdisk -F` son <inicio> <fin> <sectores> <tamaño-legible>, y el inicio
+        # es lo que convierte un hueco en algo DIBUJABLE: sin él la cara no sabe entre qué dos
+        # particiones cae, y «acá no hay nada» es justo el sitio donde más conviene instalar.
+        "$SFDISK" -F "$_ce_d" 2>/dev/null | awk -v d="$_ce_d" -v min="$(mib_minimos)" -v sb="$_ce_sb" \
+            'NF==4 && $1 ~ /^[0-9]+$/ { by=$3*sb; ib=$1*sb; m=int(by/1048576);
+               if (m >= min) print d"|-|"m"|-|-|-|LIBRE|hueco|"d"|"ib"|"by"|-|espacio sin asignar en "d;
+               else print d"|-|"m"|-|-|-|NO_TOCAR|hueco|"d"|"ib"|"by"|-|hueco sin asignar en "d", pero son "m" M y hacen falta al menos "min" M" }'
     done
 }
 
@@ -379,7 +426,7 @@ mostrar_censo() {
     [ -n "${RAIZ_MAJMIN:-}" ] || echo "  ⚠ NO pude determinar cuál es la raíz viva de esta máquina: tratá TODO con sospecha."
     printf "  %-3s %-16s %-9s %-11s %s\n" "nº" "partición" "tamaño" "qué es" "veredicto"
     _mc_n=0
-    { if [ -n "$_mc_f" ]; then cat "$_mc_f"; else censo; fi; } | while IFS='|' read -r _d _u _m _fs _lb _mn _ver _exp; do
+    { if [ -n "$_mc_f" ]; then cat "$_mc_f"; else censo; fi; } | while IFS='|' read -r _d _u _m _fs _lb _mn _ver _cla _dis _ib _tb _tg _exp; do
         _mc_n=$(( _mc_n + 1 ))
         case "$_ver" in
             CANDIDATA) _mc_marca="✓ se puede usar (SE BORRA lo que tenga)" ;;
@@ -422,26 +469,67 @@ mostrar_censo() {
 jstr() {
     printf '"%s"' "$(printf '%s' "${1:-}" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/ /g' | tr -d '\000-\010\013\014\016-\037')"
 }
+# ── discos_json — el APARATO, una vez por disco y no repetido en cada fila ─────────────────────
+#
+# `/dev/sdc` no contesta «¿cuál es el pendrive que acabo de enchufar?». El modelo y si se puede
+# desenchufar son las dos señas que lo distinguen del disco de la máquina.
+#
+# ⚠ Va en un array PROPIO y no como campos de cada partición —que es como se pidió— porque un dato
+# del disco repetido en cada fila se puede contradecir entre filas, y entonces hay que decidir a
+# cuál creerle. Cada fila trae `disco`, que es la clave acá. Y no hace falta ninguna herramienta:
+# todo sale de `/sys` y de la cabecera de `sfdisk -d`.
+#
+# ⚠ `removible` es el flag del kernel, que es lo que hay: dice «este aparato admite que le saques el
+# medio». Un USB sale 1 y un NVMe interno sale 0, que es el caso que importa — pero un lector de
+# tarjetas vacío también sale 1, así que **es una seña para pintar, no una garantía**.
+discos_json() {
+    printf '  "discos": [\n'
+    _dj_pri=1
+    for _dj_d in $(discos_del_sistema); do
+        _dj_n=$(nombre_kernel "$_dj_d")
+        _dj_mo=$(sed 's/[[:space:]]*$//' "/sys/class/block/$_dj_n/device/model" 2>/dev/null || true)
+        _dj_rm=false
+        [ "$(cat "/sys/class/block/$_dj_n/removable" 2>/dev/null || echo 0)" = 1 ] && _dj_rm=true
+        _dj_tb=$("$SFDISK" -d "$_dj_d" 2>/dev/null | sed -n 's/^label:[[:space:]]*\(.*\)$/\1/p' | head -1)
+        [ "$_dj_pri" = 1 ] || printf ',\n'
+        _dj_pri=0
+        printf '    {"dev": %s, "modelo": %s, "removible": %s, "tamano_bytes": %s, "sector_bytes": %s, "tabla": %s}' \
+            "$(jstr "$_dj_d")" \
+            "$( [ -n "${_dj_mo:-}" ] && jstr "$_dj_mo" || printf 'null' )" \
+            "$_dj_rm" \
+            "$(bytes_del_disco "$_dj_d")" \
+            "$(sector_bytes_de "$_dj_d")" \
+            "$( [ -n "${_dj_tb:-}" ] && jstr "$_dj_tb" || printf 'null' )"
+    done
+    printf '\n  ],\n'
+}
+
 censo_json() {
     _cj_f="${1:-}"
     printf '{\n'
-    printf '  "version": 1,\n'
+    printf '  "version": 2,\n'
     printf '  "raiz_viva": %s,\n' "$( [ -n "${RAIZ_MAJMIN:-}" ] && jstr "$RAIZ_MAJMIN" || printf 'null' )"
+    discos_json
     printf '  "particiones": [\n'
     _cj_pri=1
-    { if [ -n "$_cj_f" ]; then cat "$_cj_f"; else censo; fi; } | while IFS='|' read -r _d _u _m _fs _lb _mn _ver _exp; do
+    { if [ -n "$_cj_f" ]; then cat "$_cj_f"; else censo; fi; } | while IFS='|' read -r _d _u _m _fs _lb _mn _ver _cla _dis _ib _tb _tg _exp; do
         [ -n "$_d" ] || continue
         case "$_ver" in CANDIDATA|LIBRE|RAIZ_VIVA|CRUDA) _cj_us=true ;; *) _cj_us=false ;; esac
         [ "$_cj_pri" = 1 ] || printf ',\n'
         _cj_pri=0
-        printf '    {"dev": %s, "partuuid": %s, "mib": %s, "fs": %s, "etiqueta": %s, "montaje": %s, "veredicto": %s, "usable": %s, "explicacion": %s}' \
+        printf '    {"dev": %s, "partuuid": %s, "mib": %s, "fs": %s, "etiqueta": %s, "montaje": %s, "veredicto": %s, "usable": %s, "clase": %s, "disco": %s, "inicio_bytes": %s, "tamano_bytes": %s, "tipo_guid": %s, "explicacion": %s}' \
             "$(jstr "$_d")" \
             "$( [ "${_u:--}" = "-" ] && printf 'null' || jstr "$_u" )" \
             "${_m:-0}" \
             "$( [ "${_fs:--}" = "-" ] || [ -z "${_fs:-}" ] && printf 'null' || jstr "$_fs" )" \
             "$( [ "${_lb:--}" = "-" ] || [ -z "${_lb:-}" ] && printf 'null' || jstr "$_lb" )" \
             "$( [ "${_mn:--}" = "-" ] || [ -z "${_mn:-}" ] && printf 'null' || jstr "$_mn" )" \
-            "$(jstr "$_ver")" "$_cj_us" "$(jstr "$_exp")"
+            "$(jstr "$_ver")" "$_cj_us" \
+            "$(jstr "${_cla:-particion}")" \
+            "$( [ -n "${_dis:-}" ] && [ "${_dis}" != "-" ] && jstr "$_dis" || printf 'null' )" \
+            "${_ib:-0}" "${_tb:-0}" \
+            "$( [ "${_tg:--}" = "-" ] || [ -z "${_tg:-}" ] && printf 'null' || jstr "$(printf '%s' "$_tg" | tr 'A-Z' 'a-z')" )" \
+            "$(jstr "$_exp")"
     done
     printf '\n  ]\n}\n'
 }
@@ -484,7 +572,7 @@ elegir_destino() {
     echo "  Elegir una ✓ BORRA lo que tenga. «Al lado» no borra nada."
     _ed_n=0
     printf "  opciones: "
-    while IFS='|' read -r _d _u _m _fs _lb _mn _ver _exp; do
+    while IFS='|' read -r _d _u _m _fs _lb _mn _ver _cla _dis _ib _tb _tg _exp; do
         _ed_n=$(( _ed_n + 1 ))
         # ⚠ `CRUDA` tiene que estar acá. Sin ella la línea salía VACÍA en un disco cuyo único
         # destino posible era una partición sin identificar: el menú ofrecía la opción 4 y a la vez