-
+
+
+
+
+ {updateCtrl.isUpdateError ? (
+
+ ) : null}
+
+
+
+
+
diff --git a/modules/customers/src/web/view/controllers/use-customer-view-page.controller.ts b/modules/customers/src/web/view/controllers/use-customer-view-page.controller.ts
index e4f73c03..b7f03070 100644
--- a/modules/customers/src/web/view/controllers/use-customer-view-page.controller.ts
+++ b/modules/customers/src/web/view/controllers/use-customer-view-page.controller.ts
@@ -1,8 +1,8 @@
+import { useUrlParamId } from "@erp/core/hooks";
import { useCallback, useMemo } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
-import { useUrlParamId } from "@erp/core/hooks";
-import { useTranslation } from "../../../i18n";
+import { useTranslation } from "../../i18n";
import {
buildCustomerViewMissingEntityPageErrorViewModel,
buildCustomerViewPageErrorViewModel,
diff --git a/packages/rdx-ui/docs/contributing-ui.md b/packages/rdx-ui/docs/contributing-ui.md
new file mode 100644
index 00000000..78814ab4
--- /dev/null
+++ b/packages/rdx-ui/docs/contributing-ui.md
@@ -0,0 +1,101 @@
+# Contributing UI
+
+## Reglas base
+
+- todos los componentes públicos propios deben usar prefijo `Rdx`;
+- el código y los identificadores deben estar en inglés;
+- la documentación y los comentarios deben estar en español;
+- usar TypeScript estricto;
+- usar imports de tipos explícitos en cabecera;
+- no usar tipos inline tipo `import("react").ReactNode`.
+
+## Responsabilidad de `packages/rdx-ui`
+
+Los componentes de `rdx-ui`:
+
+- no conocen React Query;
+- no conocen routing;
+- no conocen DTOs;
+- no conocen entities;
+- no conocen controllers funcionales;
+- no contienen lógica de negocio.
+
+Deben recibir props ya preparadas por la capa consumidora.
+
+## Tema y color
+
+- todo debe funcionar en `light` y `dark` desde el inicio;
+- usar tokens semánticos;
+- no introducir colores físicos sin justificación fuerte y documentada;
+- no usar el color como único indicador de estado.
+
+## Accesibilidad
+
+- foco visible obligatorio;
+- botones de icono con nombre accesible;
+- no crear `div` clicables;
+- mantener orden DOM coherente;
+- usar controles reales proporcionados por el consumidor;
+- evitar wrappers vacíos que compliquen la semántica.
+
+## API pública
+
+- mantener una API pequeña y explícita;
+- no crear abstracciones genéricas sin repetición real;
+- `className` es un escape controlado, no la API principal;
+- no usar `transition-all` en nuevos componentes salvo caso excepcional documentado.
+
+## Composición de paneles
+
+- no duplicar sombras entre una primitive y su composición;
+- no duplicar el control de cierre entre capas;
+- las acciones de contexto deben llegar ya construidas desde el consumidor;
+- los labels accesibles de acciones de dominio pertenecen al consumidor;
+- una primitive reutilizable puede resolver superficie e infraestructura, mientras la composición resuelve jerarquía visual y accesibilidad contextual.
+
+## Acciones de fila
+
+- `RdxRowActions` solo renderiza acciones primarias y overflow;
+- el consumidor decide qué acciones existen y en qué orden aparecen;
+- las acciones destructivas deben ir en overflow salvo excepción justificada por código real;
+- `disabledReason` pertenece al consumidor y debe ser comprensible sin conocimiento de dominio en `rdx-ui`;
+- no mover permisos, navegación, dialogs ni entidades dentro de `packages/rdx-ui`;
+- los icon buttons deben mantener `aria-label` y tooltip accesible.
+
+## Editor simple
+
+- `RdxEditorSection` con `variant="plain"` es el patrón por defecto del editor simple;
+- usar una única superficie editable cuando sea útil separar claramente la página del área de formulario;
+- no envolver cada sección en `Card` por defecto;
+- no duplicar padding entre shell, layout y superficie editable;
+- `RdxFormGrid` debe concentrar la retícula del editor sin acoplarse a RHF;
+- las diferencias entre create y update no justifican nuevos componentes si solo cambian defaults o wiring.
+
+## Editor complejo
+
+- `RdxEditorPageLayout` debe reservar el `aside` para resumen, metadata o contexto de apoyo;
+- no mover campos editables al `aside` solo para equilibrar visualmente columnas;
+- en desktop el layout objetivo del editor complejo es `minmax(0, 1fr) ~22rem`;
+- el `aside` debe apilarse debajo del `main` cuando no haya ancho suficiente;
+- si no existe `aside`, no debe reservarse hueco lateral;
+- mantener una única superficie principal para el formulario, también en editores complejos;
+- `RdxEditorSection` puede usar `variant="panel"` dentro del `aside` cuando el bloque necesite superficie independiente;
+- `RdxFormGrid` debe quedarse en grupos de campos y no envolver tablas, line editors ni layouts funcionales especializados;
+- no mover los totales al `aside` por defecto;
+- si el editor usa footer sticky inferior, no duplicar `Guardar` y `Cancelar` completos en el header;
+- `RdxEditorStickyFooter` no conoce RHF, submit, routing ni dominio; solo compone superficie, orden, separación y responsive;
+- la región `leading` del footer puede alojar resumen o contexto, pero ese contenido sigue perteneciendo al dominio consumidor;
+- coordinar cualquier `sticky` lateral con el offset del header para evitar clipping y foco oculto.
+
+## Sincronización obligatoria
+
+Todo cambio futuro que afecte a la API pública o al comportamiento visual debe actualizar en el mismo cambio:
+
+```txt
+1. implementación
+2. documentación Markdown
+3. página interactiva UI Kit
+4. tests aplicables
+```
+
+Un cambio queda incompleto si cualquiera de esas cuatro piezas queda desincronizada.
diff --git a/packages/rdx-ui/docs/foundations.md b/packages/rdx-ui/docs/foundations.md
new file mode 100644
index 00000000..17d273b5
--- /dev/null
+++ b/packages/rdx-ui/docs/foundations.md
@@ -0,0 +1,134 @@
+# Foundations Rdx
+
+## Estado
+
+Este documento define las foundations estructurales iniciales del sistema visual Rdx.
+
+El documento canónico de contexto global es:
+
+- [visual-system-design.md](./visual-system-design.md)
+
+## Espaciado de página
+
+```txt
+mobile:
+ padding 4
+
+desktop:
+ padding 6
+```
+
+Referencia inicial:
+
+```txt
+px-4 py-4
+md:px-6 md:py-6
+```
+
+Se aplica en `RdxPageContainer`.
+
+Uso correcto:
+
+- usarlo como padding principal de página;
+- no anidarlo dentro de otro contenedor que ya aporte el mismo padding estructural;
+- si el shell ya aplica padding equivalente, el consumidor debe evitar duplicarlo.
+
+## Ritmo vertical
+
+```txt
+compact:
+ gap 4
+
+default:
+ gap 6
+
+relaxed:
+ gap 8
+```
+
+No se fija `gap-12` como foundation general.
+
+Se aplica en `RdxPageStack`.
+
+## Anchuras
+
+```txt
+full:
+ w-full
+
+wide:
+ max-w-screen-2xl mx-auto
+
+content:
+ max-w-5xl mx-auto
+```
+
+Uso recomendado:
+
+- `full`: listados, tablas, editores complejos, pantallas documentales;
+- `wide`: detalles o páginas amplias centradas;
+- `content`: editores simples y formularios de una columna lógica.
+
+## Superficies
+
+```txt
+contenido principal:
+ plano
+
+secciones:
+ separadas por espacio o divisores
+
+aside:
+ puede usar paneles
+
+tabla/editor de líneas:
+ puede usar superficie independiente
+
+card:
+ excepción, no default
+```
+
+Reglas:
+
+- no usar card como solución por defecto para toda la página;
+- evitar card dentro de card;
+- preferir espacio, jerarquía tipográfica y divisores.
+
+## Sombras
+
+```txt
+página:
+ sin sombra
+
+secciones:
+ sin sombra
+
+panel contextual:
+ sombra suave opcional
+
+sticky header/footer:
+ preferir borde
+ usar sombra mínima solo cuando sea necesaria
+```
+
+## Tema
+
+Uso obligatorio de tokens semánticos:
+
+```txt
+bg-background
+text-foreground
+bg-muted
+text-muted-foreground
+border-border
+bg-primary
+text-primary-foreground
+bg-destructive
+text-destructive-foreground
+```
+
+Reglas:
+
+- no introducir colores físicos para resolver superficies o estados;
+- no crear una nueva paleta de `success`, `warning` o `info` en esta fase si no existe convención central consolidada;
+- validar cualquier foundation nueva en `light` y `dark`.
diff --git a/packages/rdx-ui/docs/page-layouts.md b/packages/rdx-ui/docs/page-layouts.md
new file mode 100644
index 00000000..04eab419
--- /dev/null
+++ b/packages/rdx-ui/docs/page-layouts.md
@@ -0,0 +1,247 @@
+# Page Layouts Rdx
+
+## Estado
+
+Este documento describe los patrones conceptuales de página aprobados para adopción progresiva.
+
+El documento canónico de contexto global es:
+
+- [visual-system-design.md](./visual-system-design.md)
+
+## Listado
+
+```txt
+RdxPageContainer
+└── RdxPageStack
+ ├── RdxPageHeader
+ ├── notices opcionales
+ └── contenido del listado
+```
+
+Reglas:
+
+- anchura por defecto `full`;
+- acciones principales en cabecera;
+- acciones de fila frecuentes visibles y acciones destructivas en overflow mediante `RdxRowActions`;
+- panel contextual solo cuando aporte contexto real;
+- la composición del listado no debe acoplarse a React Query, routing ni dominio.
+
+Ejemplo de integración real validado en `proformas/list` y `customers/list`:
+
+```tsx
+
+
+ }
+ />
+
+ {notice}
+
+ {content}
+
+
+ {dialogs}
+
+```
+
+Aclaraciones:
+
+- `RdxPageContainer` resuelve el padding exterior y la anchura de página;
+- `RdxPageStack` resuelve el ritmo vertical principal entre cabecera, aviso y contenido;
+- `RdxPageHeader` no conoce navegación ni callbacks de React Router;
+- la tabla, los filtros, los panels, los dialogs y los controllers permanecen fuera de las foundations;
+- `RdxRowActions` puede vivir dentro de las columnas de tabla, pero recibe acciones ya decididas por el consumidor;
+- en ambos pilotos el panel contextual sigue viviendo en composición local mediante `ResizablePanelGroup`;
+- no documentar todavía `RdxListPageLayout` ni `RdxResponsivePageGrid` mientras el resize y la apertura del aside dependan del caso de uso.
+
+### Evidencia de pilotos de listado
+
+```txt
+DECISIÓN_CONFIRMADA
+ Container + Stack + Header + Actions cubren la capa exterior del listado.
+
+DECISIÓN_PROVISIONAL
+ La Page puede extraer una vista pura si mejora el test y reduce wiring.
+
+NO_ABSTRAER_TODAVÍA
+ main + aside redimensionable
+ shell específico del panel contextual
+ toolbars
+```
+
+## Listado con panel contextual
+
+```txt
+ResizablePanelGroup
+├── main
+└── RightPanel
+ └── RdxContextPanel
+```
+
+Reglas:
+
+- `ResizablePanelGroup` gestiona la geometría, el resize y la persistencia local del split;
+- `RightPanel` gestiona la superficie lateral, la altura y el overflow base;
+- `RdxContextPanel` gestiona cabecera, cierre, body y footer visual del panel;
+- el contenido interno del panel sigue siendo específico del dominio;
+- no documentar todavía un layout genérico de listado a partir de este patrón.
+
+## Editor simple
+
+```txt
+scroll container
+└── RdxEditorPageLayout complexity="simple"
+ └── form stack
+ ├── RdxEditorSection
+ ├── RdxEditorSection
+ └── ...
+```
+
+Reglas:
+
+- `RdxEditorPageLayout` resuelve padding interior, ancho centrado y `min-w-0`;
+- el editor simple puede apoyarse en una única superficie contenedora para diferenciar la página del área editable, sin volver a cards por sección;
+- `RdxEditorSection` expresa jerarquía por defecto con spacing y divisor inferior;
+- `RdxFormGrid` mantiene la retícula del formulario sin conocer RHF ni dominio;
+- priorizar lectura y velocidad de escaneo;
+- usar secciones planas con espacio o divisores;
+- reservar sticky footer solo cuando el scroll lo justifique.
+
+Ejemplo validado en `customers/update` y `customers/create`:
+
+```tsx
+
+```
+
+Deuda registrada:
+
+- `PageFormHeader` sigue siendo full-width mientras el contenido del editor queda centrado en `max-w-5xl`;
+- no modificar todavía el header legacy sin validación visual manual real.
+
+Evidencia adicional de create/update:
+
+```txt
+DECISIÓN_CONFIRMADA
+ ambos flujos reutilizan el mismo layout, superficie y secciones.
+
+ESPECÍFICO
+ defaults iniciales
+ wiring de submit
+ feedback de error
+```
+
+## Editor complejo
+
+```txt
+shell
+├── PageFormHeader
+├── scroll container
+│ └── RdxEditorPageLayout complexity="complex"
+│ ├── main
+│ │ └── única superficie editable
+│ │ ├── RdxEditorSection
+│ │ ├── RdxEditorSection
+│ │ ├── bloque de líneas con layout propio
+│ │ └── ...
+│ └── aside
+│ └── controles secundarios o contexto de apoyo
+└── RdxEditorStickyFooter
+ ├── summary/context region
+ └── action region
+```
+
+Reglas:
+
+- `RdxEditorPageLayout` resuelve padding exterior, `min-w-0` y la geometría `main + aside`;
+- el `main` debe alojar los campos editables y sus bloques funcionales;
+- el `aside` debe reservarse para metadata, configuración o contexto de apoyo ya existente en la pantalla;
+- el `aside` del editor complejo usa una geometría aproximada `minmax(0, 1fr) 22rem` en desktop y se apila debajo del `main` cuando no hay ancho suficiente;
+- no mover campos editables al `aside` solo por estética si eso empeora el flujo;
+- no mover los totales al `aside`;
+- los bloques con layout interno propio, como una tabla de líneas, no deben forzarse dentro de `RdxFormGrid`;
+- `RdxEditorStickyFooter` resuelve una única superficie sticky con una región contextual flexible y una región de acciones;
+- los totales siguen perteneciendo al dominio de Proformas aunque se rendericen dentro de la región contextual del footer;
+- `PageFormHeader` puede seguir existiendo, pero no debe duplicar `Guardar` y `Cancelar` cuando el footer sticky ya los contiene.
+
+Ejemplo validado en `proformas/update`:
+
+```tsx
+
+
+
+
+
+
+
}
+ secondary={cancelButton}
+ primary={saveButton}
+ status={status}
+ />
+
+
+```
+
+Evidencia del piloto:
+
+```txt
+DECISIÓN_CONFIRMADA
+ main contiene la única superficie editable
+ aside contiene bloques secundarios ya existentes del editor
+ la sticky zone inferior pasa a una única superficie visual
+ los totales permanecen como dominio de Proformas dentro de la región contextual
+ las acciones principales comparten el mismo footer sin una segunda franja
+ RdxFormGrid se mantiene solo para campos
+
+DECISIÓN_PROVISIONAL
+ visual exacta del header legacy frente al ancho full del editor complejo
+
+NO_ABSTRAER_TODAVÍA
+ layout específico del bloque de líneas
+ layout específico del footer de totales
+```
+
+## Detalle
+
+```txt
+width="wide" o "full"
+main + aside opcional
+aside para metadata, actividad, resumen o acciones
+```
+
+Reglas:
+
+- mantener patrón consistente de label/value;
+- usar aside solo cuando el contenido realmente gane claridad;
+- no implementar todavía `RdxDetailPageLayout` en esta fase.
diff --git a/packages/rdx-ui/docs/visual-system-design.md b/packages/rdx-ui/docs/visual-system-design.md
new file mode 100644
index 00000000..0059edc1
--- /dev/null
+++ b/packages/rdx-ui/docs/visual-system-design.md
@@ -0,0 +1,1225 @@
+# Frontend ERP — Sistema visual Rdx para páginas y patrones UI/UX
+
+## 1. Estado
+
+**DISEÑO APROBADO PARA IMPLEMENTACIÓN PROGRESIVA**
+
+Este documento consolida las decisiones visuales, arquitectónicas y de adopción para construir un sistema UI/UX uniforme en el frontend del ERP.
+
+El sistema se implementará dentro de:
+
+```txt
+packages/rdx-ui
+```
+
+Todos los componentes públicos propios del sistema deberán usar el prefijo:
+
+```txt
+Rdx
+```
+
+Ejemplos:
+
+```txt
+RdxPageContainer
+RdxPageHeader
+RdxPageActions
+RdxContextPanel
+RdxRowActions
+RdxListPageLayout
+RdxEditorPageLayout
+RdxEditorSection
+RdxFormGrid
+RdxEditorStickyFooter
+RdxDetailPageLayout
+```
+
+El prefijo distingue los componentes propios de la aplicación frente a primitives de terceros como Shadcn/UI y evita colisiones de naming.
+
+---
+
+## 2. Contexto
+
+El frontend ERP está basado en:
+
+- React 19
+- TypeScript estricto
+- Shadcn/UI
+- Tailwind CSS v4
+- React Hook Form
+- TanStack Query
+- TanStack Table
+- React Router
+
+La arquitectura general mantiene el flujo:
+
+```txt
+API → DTO → adapter → Entity → hook → controller → page controller → UI
+```
+
+Separación de responsabilidades:
+
+```txt
+shared/api → transporte remoto
+shared/adapters → DTO → Entity
+shared/hooks → React Query y entities
+controllers → orquestación del caso de uso
+page controllers → composición e integración externa
+Page → wiring y composición
+UI → render puro
+```
+
+El sistema visual Rdx debe respetar estrictamente esta arquitectura.
+
+---
+
+## 3. Objetivo
+
+Crear un estándar visual y estructural común para los principales tipos de pantalla del ERP:
+
+```txt
+Listados
+Vistas de detalle
+Pantallas de creación
+Pantallas de edición
+```
+
+El sistema debe unificar:
+
+- márgenes;
+- padding;
+- separación entre elementos;
+- anchuras;
+- jerarquía tipográfica;
+- disposición de acciones;
+- iconografía;
+- estados visuales;
+- superficies;
+- responsive;
+- accesibilidad;
+- soporte claro/oscuro;
+- documentación y ejemplos interactivos.
+
+El objetivo no es crear un framework CRUD universal, sino una biblioteca pequeña de primitives y composiciones visuales explícitas.
+
+---
+
+## 4. Principios rectores
+
+### 4.1 Claridad antes que abstracción
+
+```txt
+Primero claridad local.
+Después repetición real.
+Solo entonces abstraer.
+```
+
+No se crearán componentes genéricos sin evidencia suficiente de repetición.
+
+### 4.2 UI pura
+
+Los componentes Rdx no deben conocer:
+
+- React Query;
+- DTOs;
+- APIs;
+- controllers;
+- routing;
+- permisos de negocio;
+- estados de dominio;
+- errores de transporte.
+
+Deben recibir props ya preparadas.
+
+### 4.3 Composición, no configuración excesiva
+
+Preferir:
+
+```tsx
+
+
+
+
+
+
+```
+
+Evitar:
+
+```tsx
+
+```
+
+### 4.4 Diseño compacto y ordenado
+
+La interfaz debe:
+
+- evitar espacio vacío que no aporte jerarquía;
+- mantener alta densidad informativa;
+- conservar suficiente separación para lectura;
+- evitar ruido visual;
+- evitar card dentro de card;
+- usar superficies solo cuando tengan una función clara.
+
+### 4.5 Tema claro y oscuro desde el inicio
+
+Todo componente debe diseñarse y validarse en:
+
+```txt
+Tema claro
+Tema oscuro
+```
+
+No se aceptará una implementación diseñada solo para tema claro y corregida posteriormente.
+
+---
+
+## 5. Referencias visuales
+
+### 5.1 Pantalla de referencia principal
+
+La pantalla de listado de proformas es la referencia visual inicial para:
+
+- densidad;
+- toolbar;
+- espaciado;
+- jerarquía de acciones;
+- tratamiento de tabla;
+- iconografía;
+- filtros;
+- composición general.
+
+Ruta:
+
+```txt
+modules/customer-invoices/src/web/proformas/list
+```
+
+Estado de adopción a 6 de agosto de 2026:
+
+- primera pantalla piloto migrada a `RdxPageContainer`, `RdxPageStack`, `RdxPageHeader` y `RdxPageActions`;
+- se ha sustituido el header legacy solo en la Page de listado, sin tocar controllers, grid, filtros, panel lateral ni dialogs;
+- el piloto confirma que las foundations actuales cubren la geometría exterior de la página sin justificar todavía `RdxListPageLayout`, `RdxResponsivePageGrid` ni `RdxContextPanel`.
+- segundo piloto migrado en `modules/customers/src/web/list` con el mismo perímetro de cambio;
+- ambos listados confirman la reutilización de container, stack, header y CTA principal, pero todavía no justifican abstraer la geometría interior del listado;
+- los dos paneles contextuales reales confirman una separación válida entre `RightPanel` como primitive de superficie y `RdxContextPanel` como composición visual contextual.
+
+### 5.2 Pantallas de contraste
+
+Se usarán además:
+
+```txt
+modules/customers/src/web/list
+modules/customers/src/web/create
+modules/customers/src/web/update
+modules/customers/src/web/view
+modules/customer-invoices/src/web/proformas/create
+modules/customer-invoices/src/web/proformas/update
+modules/customer-invoices/src/web/proformas/detail
+```
+
+Estas pantallas permiten validar listados, editor simple, editor complejo y detalle.
+
+---
+
+## 6. Tipos de pantalla
+
+## 6.1 Listados
+
+Reglas:
+
+```txt
+Anchura completa
+Toolbar compacta
+Tabla densa
+Acción principal en cabecera
+Panel lateral opcional
+Paginación integrada
+Estados vacíos y errores consistentes
+```
+
+El listado no debe estar obligado a usar filtros, panel lateral o tabs.
+
+## 6.2 Editor simple
+
+Reglas:
+
+```txt
+Una columna por defecto
+Ancho máximo centrado
+Lateral opcional
+No reservar espacio si el lateral no existe
+Secciones planas con divisores
+```
+
+Ejemplos:
+
+```txt
+customers/create
+customers/update
+suppliers/create
+suppliers/update
+```
+
+## 6.3 Editor complejo
+
+Reglas:
+
+```txt
+Todo el ancho disponible
+Main flexible
+Lateral derecho permitido como región estructural estándar
+Footer sticky cuando exista scroll significativo
+Paneles laterales para resumen, configuración o contexto
+```
+
+Ejemplos:
+
+```txt
+proformas/update
+issued-invoices/update, si existiera
+```
+
+## 6.4 Vista de detalle
+
+Reglas:
+
+```txt
+Anchura wide o full
+Main + lateral opcional
+Lateral para metadata, actividad, resumen o acciones
+Patrón label/value consistente
+```
+
+---
+
+## 7. Política de anchuras
+
+Se define el siguiente contrato inicial:
+
+```ts
+export type RdxPageWidth = "full" | "wide" | "content";
+```
+
+### `full`
+
+Uso:
+
+- listados;
+- editores complejos;
+- tablas;
+- pantallas documentales.
+
+### `wide`
+
+Uso:
+
+- vistas de detalle;
+- pantallas con mucha información sin necesidad de ocupar todo el viewport.
+
+### `content`
+
+Uso:
+
+- formularios y editores simples.
+
+Referencia inicial:
+
+```txt
+max-w-5xl o max-w-6xl
+```
+
+La decisión exacta se centraliza en `RdxPageContainer`.
+
+---
+
+## 8. Foundations visuales
+
+## 8.1 Espaciado
+
+Escala inicial recomendada:
+
+```txt
+compact → 1rem
+normal → 1.5rem
+relaxed → 2rem
+```
+
+Propuesta de uso:
+
+```txt
+Page padding mobile: 1rem
+Page padding desktop: 1.5rem
+Main block gap: 1.5rem
+Section internal gap: 1rem
+Form grid horizontal: 1.5rem
+Form grid vertical: 1.25rem
+Compact element gap: 0.5–0.75rem
+```
+
+No se fija `gap-12` como foundation global.
+
+## 8.2 Tipografía
+
+Funciones tipográficas:
+
+```txt
+Page title
+Page description
+Section title
+Section description
+Field label
+Body
+Metadata
+Numeric value
+Status
+```
+
+Referencia inicial:
+
+```txt
+Page title: text-2xl font-semibold tracking-tight
+Page description: text-sm text-muted-foreground
+Section title: text-base font-semibold
+Section description: text-sm text-muted-foreground
+Metadata label: text-xs font-medium text-muted-foreground
+Numeric value: font-medium tabular-nums
+```
+
+## 8.3 Superficies
+
+Política:
+
+```txt
+Contenido principal → plano
+Secciones → divisores y spacing
+Aside → paneles permitidos
+Tabla compleja → superficie independiente permitida
+Notices → superficie semántica
+Cards → excepción, no default
+```
+
+## 8.4 Bordes y radios
+
+Reglas:
+
+- evitar border en cada bloque;
+- preferir `border-b` o `Separator` entre secciones;
+- reservar radios para paneles o superficies independientes;
+- los radios hijos no deben exceder los radios del contenedor padre.
+
+## 8.5 Sombras
+
+Reglas:
+
+```txt
+Página y secciones → sin sombra
+Panel contextual → sombra suave opcional
+Header/footer sticky → borde como primera opción
+```
+
+Las sombras deben ser mínimas.
+
+## 8.6 Color
+
+Usar tokens semánticos:
+
+```txt
+background
+foreground
+card
+muted
+muted-foreground
+border
+primary
+primary-foreground
+destructive
+destructive-foreground
+```
+
+Se deberán consolidar también tokens para:
+
+```txt
+success
+warning
+info
+```
+
+No usar directamente colores físicos cuando exista un token semántico adecuado.
+
+Evitar:
+
+```txt
+text-green-600
+bg-yellow-100
+border-blue-300
+```
+
+---
+
+## 9. Arquitectura de componentes
+
+## 9.1 Ubicación
+
+```txt
+packages/rdx-ui/src/components/
+```
+
+Estructura objetivo progresiva:
+
+```txt
+components/
+├── layout/
+├── page-states/
+├── list-page/
+├── detail-page/
+├── editor-page/
+├── form/
+└── right-panel/
+```
+
+No es obligatorio crear toda la estructura desde la primera fase.
+
+## 9.2 Naming
+
+Todos los componentes públicos propios usan `Rdx`.
+
+Ejemplo:
+
+```ts
+export interface RdxPageHeaderProps {
+ // ...
+}
+
+export const RdxPageHeader = ({
+ // ...
+}: RdxPageHeaderProps) => {
+ // ...
+};
+```
+
+No es obligatorio usar `Rdx` en:
+
+- helpers internos;
+- tipos privados;
+- variantes locales;
+- tokens CSS.
+
+---
+
+## 10. Foundations implementadas y siguientes candidatos
+
+Fase 1 implementada:
+
+```txt
+RdxPageContainer
+RdxPageStack
+RdxPageHeader
+RdxPageActions
+RdxContextPanel
+RdxRowActions
+RdxEditorPageLayout
+RdxEditorSection
+RdxFormGrid
+RdxEditorStickyFooter
+```
+
+Candidatos posteriores a validar con pantallas piloto reales:
+
+```txt
+RdxResponsivePageGrid
+RdxListPageLayout
+```
+
+También deberán evaluarse y adaptar, no necesariamente reescribir:
+
+```txt
+PageHeader
+PageFormHeader
+RightPanel
+DataTable
+```
+
+No se implementarán todavía:
+
+```txt
+RdxDetailPageLayout
+RdxDetailField
+RdxNotice
+RdxEmptyState
+```
+
+Estos componentes se diseñarán en fases posteriores con pantallas piloto específicas.
+
+---
+
+## 11. Contratos iniciales
+
+## 11.1 `RdxPageContainer`
+
+Responsabilidad:
+
+- ancho;
+- padding exterior;
+- centrado;
+- comportamiento responsive.
+
+```ts
+export interface RdxPageContainerProps {
+ children: React.ReactNode;
+ width?: RdxPageWidth;
+ className?: string;
+}
+```
+
+No debe gestionar ritmo vertical.
+
+## 11.2 `RdxPageStack`
+
+Responsabilidad:
+
+- separación vertical entre bloques principales.
+
+```ts
+export type RdxPageStackSpacing = "compact" | "default" | "relaxed";
+
+export interface RdxPageStackProps {
+ children: React.ReactNode;
+ spacing?: RdxPageStackSpacing;
+ className?: string;
+}
+```
+
+## 11.3 `RdxPageHeader`
+
+Responsabilidad:
+
+- título;
+- descripción;
+- leading opcional;
+- metadata opcional;
+- acciones;
+
+```ts
+export interface RdxPageHeaderProps {
+ title: React.ReactNode;
+ description?: React.ReactNode;
+ leading?: React.ReactNode;
+ metadata?: React.ReactNode;
+ actions?: React.ReactNode;
+ className?: string;
+}
+```
+
+No debe conocer routing.
+
+## 11.4 `RdxPageActions`
+
+Responsabilidad:
+
+- agrupar acciones;
+- diferenciar primarias, secundarias y overflow;
+- resolver layout responsive.
+
+```ts
+export interface RdxPageActionsProps {
+ primary?: React.ReactNode;
+ secondary?: React.ReactNode;
+ overflow?: React.ReactNode;
+ className?: string;
+}
+```
+
+No debe decidir permisos ni disponibilidad.
+
+## 11.5 `RdxResponsivePageGrid`
+
+Responsabilidad:
+
+- layout `main + aside`;
+- stacking responsive;
+- ancho limitado del aside.
+
+```ts
+export type RdxPageAsideWidth = "sm" | "md" | "lg";
+export type RdxPageAsideBehavior = "static" | "sticky";
+
+export interface RdxResponsivePageGridProps {
+ main: React.ReactNode;
+ aside?: React.ReactNode;
+ asideWidth?: RdxPageAsideWidth;
+ asideBehavior?: RdxPageAsideBehavior;
+ className?: string;
+ mainClassName?: string;
+ asideClassName?: string;
+}
+```
+
+Reglas:
+
+```txt
+Desktop → main + aside
+Tablet/móvil → aside debajo de main
+Sin aside → no reservar espacio
+Sticky → variante explícita
+```
+
+## 11.6 `RdxListPageLayout`
+
+Responsabilidad:
+
+- geometría del área de listado;
+- composición `main + aside`.
+
+```ts
+export interface RdxListPageLayoutProps {
+ main: React.ReactNode;
+ aside?: React.ReactNode;
+ className?: string;
+}
+```
+
+No debe conocer:
+
+- DataTable;
+- búsqueda;
+- filtros;
+- selección;
+- React Query;
+- estado remoto;
+- dominio.
+
+## 11.7 `RdxRowActions`
+
+Responsabilidad:
+
+- renderizar acciones visibles;
+- renderizar acciones overflow;
+- representar disabled y destructive;
+- ofrecer tooltips y labels accesibles.
+
+```ts
+export interface RdxRowAction {
+ id: string;
+ label: string;
+ icon: React.ReactNode;
+ onSelect: () => void;
+ disabled?: boolean;
+ disabledReason?: string;
+ destructive?: boolean;
+}
+
+export interface RdxRowActionsProps {
+ primaryActions?: RdxRowAction[];
+ overflowActions?: RdxRowAction[];
+ overflowLabel: string;
+ className?: string;
+}
+```
+
+El componente no decide qué acciones existen.
+La UI solo renderiza; el consumidor conserva permisos, labels, disabled reasons y callbacks.
+
+---
+
+## 12. Política de acciones
+
+## 12.1 Listados
+
+```txt
+Acción principal → cabecera
+Acciones frecuentes → visibles en fila
+Acciones infrecuentes → overflow
+Acciones destructivas → overflow por defecto
+```
+
+Orden recomendado:
+
+```txt
+Ver
+Editar
+Más acciones
+```
+
+## 12.2 Formularios cortos
+
+Acciones en cabecera o al final del formulario.
+
+## 12.3 Formularios largos
+
+Acciones principales en footer sticky.
+
+## 12.4 Editores complejos
+
+```txt
+Cabecera → navegación, estado y acciones documentales
+Footer sticky → cancelar, guardar y submit principal
+```
+
+La ubicación depende de la distancia real del usuario a la acción primaria, no solo del tipo CRUD.
+
+---
+
+## 13. Política de iconos
+
+Catálogo inicial:
+
+| Acción | Icono recomendado |
+|---|---|
+| Ver | `Eye` |
+| Editar | `Pencil` |
+| Crear | `Plus` |
+| Eliminar | `Trash2` |
+| Descargar | `Download` |
+| Imprimir | `Printer` |
+| Duplicar | `Copy` |
+| Más acciones | `Ellipsis` |
+| Volver | `ArrowLeft` |
+| Guardar | `Save` |
+| Filtrar | `ListFilter` |
+| Buscar | `Search` |
+| Configurar columnas | `Columns3` |
+| Cerrar | `X` |
+
+Reglas:
+
+- iconos con tamaño consistente;
+- botón solo icono con `aria-label`;
+- tooltip cuando la acción no sea inequívoca;
+- no usar iconos diferentes para la misma acción sin justificación.
+
+---
+
+## 14. Responsive
+
+## 14.1 Listados
+
+```txt
+Desktop → toolbar horizontal y tabla completa
+Tablet → filtros colapsables cuando sea necesario
+Mobile → scroll horizontal o adaptación específica
+```
+
+No se implementará todavía una vista móvil universal de cards para tablas.
+
+## 14.2 Editor simple
+
+```txt
+Desktop → ancho máximo centrado
+Mobile → una columna
+```
+
+## 14.3 Editor complejo
+
+```txt
+Desktop → main + aside
+Tablet/móvil → aside debajo de main
+Footer sticky → debe respetar safe areas
+```
+
+## 14.4 Detalle
+
+```txt
+Desktop → main + aside opcional
+Tablet/móvil → aside debajo de main
+```
+
+---
+
+## 15. Accesibilidad
+
+Reglas obligatorias:
+
+- un único `h1` por página;
+- jerarquía correcta de headings;
+- botones reales para acciones;
+- links reales para navegación;
+- foco visible;
+- botones de icono con `aria-label`;
+- estados no representados solo mediante color;
+- `type="button"` en botones internos de formularios;
+- targets táctiles suficientes;
+- tooltips accesibles;
+- dialogs con restauración de foco;
+- loading que conserve el significado de la acción;
+- acciones disabled con explicación cuando sea útil.
+
+---
+
+## 16. Reutilización de componentes existentes
+
+Antes de crear un componente nuevo se debe comparar su contrato con:
+
+```txt
+modules/core/src/web/components/page-header.tsx
+modules/core/src/web/components/form/form-actions/page-form-header.tsx
+modules/core/src/web/components/page-error-state.tsx
+modules/core/src/web/components/error-alert.tsx
+packages/rdx-ui/src/components/right-panel/right-panel.tsx
+packages/rdx-ui/src/components/datatable/data-table.tsx
+packages/rdx-ui/src/components/form/form-section-card.tsx
+packages/rdx-ui/src/components/form/form-section-grid.tsx
+```
+
+Proceso obligatorio:
+
+```txt
+1. Definir contrato objetivo Rdx.
+2. Comparar con componente existente.
+3. Adaptar si es viable.
+4. Exportar con naming Rdx.
+5. Mantener alias temporal si hay consumidores.
+6. Migrar progresivamente.
+7. Retirar alias cuando no se use.
+```
+
+No se harán renombrados globales sin estrategia de compatibilidad.
+
+---
+
+## 17. Documentación
+
+## 17.1 Markdown
+
+Ubicación canónica:
+
+```txt
+packages/rdx-ui/docs/
+├── visual-system-design.md
+├── foundations.md
+├── page-layouts.md
+└── contributing-ui.md
+```
+
+## 17.2 UI Kit interactivo
+
+Se creará una ruta interna:
+
+```txt
+/development/ui-kit
+```
+
+El UI Kit debe mostrar:
+
+- foundations;
+- componentes aislados;
+- variantes;
+- estados;
+- tema claro y oscuro;
+- comportamiento responsive;
+- composiciones completas;
+- ejemplos correctos;
+- anti-patterns relevantes.
+
+Ejemplos completos mínimos:
+
+```txt
+Listado
+Detalle
+Editor simple
+Editor complejo
+Error
+Vacío
+Loading
+```
+
+## 17.3 Regla de sincronización
+
+Todo cambio que afecte a API pública, comportamiento o apariencia debe actualizar en la misma intervención:
+
+```txt
+1. implementación;
+2. documentación Markdown;
+3. UI Kit interactivo;
+4. tests aplicables.
+```
+
+Un cambio se considera incompleto si alguno de estos recursos queda desactualizado.
+
+---
+
+## 18. Testing
+
+Los tests deben centrarse en comportamiento:
+
+- render de slots;
+- variantes;
+- accesibilidad;
+- acciones disabled;
+- overflow;
+- responsive inferible por clases;
+- ausencia de columna aside cuando no se proporciona;
+- compatibilidad con tema.
+
+No testear detalles internos irrelevantes.
+
+Herramientas:
+
+```txt
+Testing Library
+Vitest
+```
+
+---
+
+## 19. Plan de implementación
+
+## Fase 0 — Documentación y UI Kit base
+
+Crear o mantener sincronizados:
+
+```txt
+packages/rdx-ui/docs/visual-system-design.md
+packages/rdx-ui/docs/foundations.md
+packages/rdx-ui/docs/page-layouts.md
+packages/rdx-ui/docs/contributing-ui.md
+```
+
+Crear la infraestructura mínima de `/development/ui-kit`.
+
+## Fase 1 — Foundations de página
+
+Implementar:
+
+```txt
+RdxPageContainer
+RdxPageStack
+RdxPageHeader
+RdxPageActions
+```
+
+## Fase 2 — Piloto `proformas/list`
+
+Aplicar foundations sin rediseño funcional.
+
+Objetivos:
+
+```txt
+Misma funcionalidad
+Misma densidad
+Menos clases estructurales locales
+Contrato reusable validado
+```
+
+Resultado observado:
+
+- `RdxPageContainer` absorbe el padding exterior de página y evita mantener `p-6` local;
+- `RdxPageStack` absorbe el ritmo vertical principal y evita mantener `space-y-6` local;
+- `RdxPageHeader` cubre la cabecera de listado con composición directa;
+- `RdxPageActions` cubre correctamente una acción primaria única;
+- el layout interno del listado sigue necesitando composición específica local mientras no se compare este piloto con `customers/list`.
+
+## Fase 3 — Layout de listado y panel
+
+Implementar:
+
+```txt
+RdxResponsivePageGrid
+RdxListPageLayout
+```
+
+Validar con:
+
+```txt
+proformas/list
+customers/list
+```
+
+Estado tras comparar ambos pilotos:
+
+- `RdxListPageLayout`: prematuro;
+- `RdxResponsivePageGrid`: prematuro en listados que dependen de `ResizablePanelGroup`;
+- `RdxContextPanel`: candidato confirmado para una fase específica sobre `RightPanel`, `CustomerSummaryPanel` y `ProformaSummaryPanel`.
+
+## Fase 4 — Panel contextual
+
+Normalizar:
+
+```txt
+RightPanel
+RdxContextPanel
+CustomerSummaryPanel
+ProformaSummaryPanel
+```
+
+Resultado esperado:
+
+- `RightPanel` mantiene la superficie lateral reusable;
+- `RdxContextPanel` concentra cabecera, cierre, jerarquía y footer contextual;
+- no existe doble header;
+- no existe doble cierre;
+- no existe doble sombra.
+
+## Fase 5 — Acciones de fila
+
+Implementar `RdxRowActions` tomando proformas como referencia.
+
+Resultado observado a 6 de agosto de 2026:
+
+- `RdxRowActions` ya existe en `packages/rdx-ui` y se exporta públicamente;
+- `proformas/list` y `customers/list` consumen el mismo contrato visual;
+- las acciones destructivas quedan en overflow;
+- el consumidor sigue decidiendo permisos, estado, callbacks y razones de disabled;
+- en `customers/list` se ha corregido el bug que renderizaba delete cuando no existía `onDeleteClick`.
+
+## Fase 6 — Editor simple
+
+Usar `customers/update` como piloto para:
+
+```txt
+RdxEditorPageLayout
+RdxEditorSection
+RdxFormGrid
+```
+
+Resultado observado a 6 de agosto de 2026:
+
+- `RdxEditorPageLayout` absorbe el ancho centrado y el padding interior del editor simple;
+- `RdxEditorSection` sustituye en `customers/update` el uso sistemático de `FormSectionCard` cuando la superficie no necesita card;
+- `RdxFormGrid` consolida la retícula ya existente sin mover lógica de formulario fuera del consumidor;
+- `PageFormHeader` se mantiene sin cambios funcionales;
+- queda pendiente validar visualmente si el header sticky full-width se percibe demasiado separado del ancho centrado del editor.
+
+## Fase 7 — Confirmación de editor simple
+
+Validar el mismo patrón en:
+
+```txt
+customers/create
+```
+
+Resultado observado a 6 de agosto de 2026:
+
+- `customers/create` reutiliza `RdxEditorPageLayout`, `RdxEditorSection` y `RdxFormGrid` sin requerir nuevas primitives;
+- create y update comparten shell, header legacy, superficie editable única y secciones planas internas;
+- las diferencias reales quedan en defaults, submit y wiring del controller, no en la composición visual;
+- `PageFormHeader` sigue siendo funcionalmente suficiente, aunque se mantiene una deuda visual menor por su anchura full-width frente al contenido centrado.
+
+## Fase 8 — Editor complejo
+
+Usar `proformas/update` como piloto para:
+
+```txt
+main + aside
+sticky footer
+totales
+acciones según scroll
+```
+
+Resultado observado a 10 de agosto de 2026:
+
+- `proformas/update` valida `RdxEditorPageLayout` con `complexity="complex"` a ancho completo, `main` flexible y `aside` estructural aproximado de `22rem`;
+- el `main` concentra la única superficie editable real del documento y el bloque de líneas mantiene su layout propio;
+- el `aside` conserva únicamente controles secundarios ya presentes en la pantalla, sin convertirlo en `RightPanel` ni mover allí los totales;
+- el footer sticky de totales se mantiene como dominio visual y económico de Proformas;
+- `RdxEditorStickyFooter` queda justificado para combinar en una única superficie sticky la región de totales/contexto y las acciones `Cancelar` / `Guardar`;
+- `PageFormHeader` queda reducido a navegación y contexto solo en `proformas/update`, sin cambio global del componente legacy;
+- `RdxEditorSection` y `RdxFormGrid` siguen siendo válidos para bloques de campos, pero no deben forzar el layout del editor de líneas ni el footer de totales.
+
+## Fase 8 — Detalle
+
+Usar primero `proformas/detail` y después `customers/view`.
+
+---
+
+## 20. Anti-patterns prohibidos
+
+No crear:
+
+```txt
+RdxCrudPage
+RdxCrudEditor
+RdxGenericEntityPage
+RdxUniversalToolbar
+RdxFormSectionEngine
+GenericPageFactory
+```
+
+No hacer:
+
+- routing dentro de `packages/rdx-ui`;
+- React Query dentro de componentes Rdx;
+- interpretación de `ApiClientError` en UI;
+- permisos dentro de `RdxRowActions`;
+- hardcodes de dominio;
+- colors físicos cuando exista token semántico;
+- megacomponentes configurables;
+- migraciones globales sin alias temporal;
+- abstracciones genéricas antes de validarlas en varias pantallas.
+
+---
+
+## 21. Evidencia de pilotos de listado
+
+### DECISIÓN_CONFIRMADA
+
+- `RdxPageContainer` resuelve de forma estable el padding exterior y la anchura de `proformas/list` y `customers/list`.
+- `RdxPageStack` resuelve el ritmo vertical principal entre cabecera, error inline y contenido.
+- `RdxPageHeader` sustituye correctamente al `PageHeader` legacy en ambos listados sin conocer navegación.
+- `RdxPageActions` cubre la CTA principal de cabecera en ambos pilotos sin necesidad de variantes adicionales.
+- `RightPanel` es suficiente como primitive de superficie lateral y no debe renombrarse globalmente.
+- `RdxContextPanel` está justificado como composición visual común sobre `RightPanel`.
+- `RdxRowActions` está justificado para normalizar icon buttons, overflow, disabled reason y destructivas en tablas.
+- `RdxEditorPageLayout`, `RdxEditorSection` y `RdxFormGrid` quedan confirmados tras migrar `customers/update` y `customers/create`.
+- `RdxEditorPageLayout` también es apto para `proformas/update` cuando el `aside` contiene bloques secundarios ya existentes y el `main` conserva la única superficie editable.
+- `RdxEditorSection` soporta tanto secciones planas de formulario como grupos secundarios del `aside` sin volver a `Card` por defecto.
+- `RdxFormGrid` queda confirmado para campos en editores complejos, pero no debe envolver tablas o editores de líneas.
+- `RdxEditorStickyFooter` está justificado para el patrón de editor complejo con una sola zona inferior persistente y sin duplicidad de acciones con el header.
+
+### DECISIÓN_PROVISIONAL
+
+- La extracción de una Page View pura es útil cuando reduce wiring y facilita tests sin controllers reales, pero no debe convertirse todavía en convención global.
+- El patrón `main + aside` existe en ambos listados, pero hoy depende de `ResizablePanelGroup`, tamaños y apertura específicos de cada pantalla.
+- El contrato final del footer contextual puede seguir afinándose cuando aparezcan paneles con resúmenes o acciones más heterogéneas.
+- `RdxEditorPageLayout` ya cubre el editor simple, pero la convivencia visual con `PageFormHeader` debe seguir validándose antes de tocar el header legacy.
+- la densidad final de la sticky zone inferior en móvil sigue requiriendo validación visual manual en múltiples resoluciones y tema oscuro.
+
+### NO_ABSTRAER_TODAVÍA
+
+- `RdxListPageLayout`: la geometría exterior ya queda resuelta por `RdxPageContainer`, `RdxPageStack` y la propia Page.
+- `RdxResponsivePageGrid`: no encaja todavía como abstracción para listados con resize persistente.
+- layouts superiores de listado mientras `ResizablePanelGroup` siga gobernando el split real.
+- un layout superior específico de editor complejo mientras `RdxEditorPageLayout` + `RdxEditorStickyFooter` sigan cubriendo la geometría exterior.
+
+---
+
+## 22. Criterios de aceptación
+
+La fase inicial se considerará correcta si:
+
+- `packages/rdx-ui` contiene los primeros componentes Rdx;
+- los contratos son puramente visuales;
+- no existe dependencia de routing, datos o dominio;
+- `proformas/list` puede adoptarlos sin perder comportamiento;
+- la documentación Markdown está actualizada;
+- el UI Kit muestra los componentes y sus variantes;
+- tema claro y oscuro están validados;
+- los tests aplicables pasan;
+- no se ha creado un framework CRUD;
+- las clases estructurales repetidas disminuyen;
+- la Page sigue componiendo y haciendo wiring explícito.
+
+---
+
+## 23. Regla mental final
+
+```txt
+Rdx define geometría, jerarquía y comportamiento visual.
+La Page compone.
+Los controllers orquestan.
+La UI no interpreta dominio ni transporte.
+```
+
+Y como criterio de calidad:
+
+```txt
+Si una abstracción ahorra pocas líneas pero empeora la lectura, no se usa.
+Si un componente necesita conocer el caso de uso, no pertenece a rdx-ui.
+Si un cambio visual no actualiza documentación y UI Kit, está incompleto.
+```
diff --git a/packages/rdx-ui/package.json b/packages/rdx-ui/package.json
index e7315207..c9644aa8 100644
--- a/packages/rdx-ui/package.json
+++ b/packages/rdx-ui/package.json
@@ -6,6 +6,7 @@
"sideEffects": false,
"scripts": {
"typecheck": "tsc -p tsconfig.json --noEmit",
+ "test": "node --import tsx --test ./src/components/layout/rdx-page-foundations.test.tsx ./src/components/context-panel/rdx-context-panel.test.tsx ./src/components/row-actions/rdx-row-actions.test.tsx ./src/components/editor-page/rdx-editor-foundations.test.tsx",
"ui:lint": "biome lint --fix",
"check": "biome check .",
"lint": "biome lint .",
@@ -37,6 +38,7 @@
"esbuild-plugin-react18-css": "^0.0.4",
"rimraf": "^6.1.3",
"tailwindcss": "^4.2.2",
+ "tsx": "^4.21.0",
"tsup": "^8.5.1",
"tw-animate-css": "^1.4.0",
"typescript": "^6.0.2",
@@ -66,4 +68,4 @@
"sonner": "^2.0.7",
"zod": "^4.3.6"
}
-}
\ No newline at end of file
+}
diff --git a/packages/rdx-ui/src/components/context-panel/index.ts b/packages/rdx-ui/src/components/context-panel/index.ts
new file mode 100644
index 00000000..e4019133
--- /dev/null
+++ b/packages/rdx-ui/src/components/context-panel/index.ts
@@ -0,0 +1 @@
+export * from "./rdx-context-panel.tsx";
diff --git a/packages/rdx-ui/src/components/context-panel/rdx-context-panel.test.tsx b/packages/rdx-ui/src/components/context-panel/rdx-context-panel.test.tsx
new file mode 100644
index 00000000..73f43b32
--- /dev/null
+++ b/packages/rdx-ui/src/components/context-panel/rdx-context-panel.test.tsx
@@ -0,0 +1,121 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+import { isValidElement } from "react";
+import { renderToStaticMarkup } from "react-dom/server";
+
+import { RdxContextPanel } from "./rdx-context-panel.tsx";
+
+const resolveElement = (node: unknown): unknown => {
+ if (Array.isArray(node)) {
+ return node.map(resolveElement);
+ }
+
+ if (!isValidElement(node)) {
+ return node;
+ }
+
+ if (typeof node.type === "function") {
+ return resolveElement(node.type(node.props));
+ }
+
+ return {
+ type: node.type,
+ props: {
+ ...node.props,
+ children: resolveElement(node.props.children),
+ },
+ };
+};
+
+const findElementsByType = (
+ node: unknown,
+ type: string
+): Array<{ type: string; props: Record
}> => {
+ if (Array.isArray(node)) {
+ return node.flatMap((child) => findElementsByType(child, type));
+ }
+
+ if (!node || typeof node !== "object" || !("type" in node) || !("props" in node)) {
+ return [];
+ }
+
+ const element = node as { type: string; props: Record };
+ const children = findElementsByType(element.props.children, type);
+
+ if (element.type === type) {
+ return [element, ...children];
+ }
+
+ return children;
+};
+
+test("renderiza título semántico, descripción, actions, children y footer", () => {
+ const html = renderToStaticMarkup(
+ Edit}
+ closeLabel="Cerrar panel"
+ description="Context description"
+ footer={Footer content
}
+ onClose={() => undefined}
+ title="Context title"
+ >
+ Panel body
+
+ );
+
+ assert.match(html, /]*>Context title<\/h2>/);
+ assert.match(html, /Context description/);
+ assert.match(html, /Edit/);
+ assert.match(html, /Panel body/);
+ assert.match(html, /Footer content/);
+ assert.match(html, /Cerrar panel/);
+});
+
+test("no renderiza regiones opcionales vacías y combina className", () => {
+ const html = renderToStaticMarkup(
+
+ Body
+
+ );
+
+ assert.match(html, /custom-panel/);
+ assert.match(html, /]*>Only title<\/h2>/);
+ assert.equal((html.match(/Cerrar panel/g) ?? []).length, 0);
+ assert.equal((html.match(/
{
+ let wasClosed = false;
+
+ const resolved = resolveElement(
+ {
+ wasClosed = true;
+ }}
+ title="Context title"
+ >
+ Body
+
+ );
+
+ const buttons = findElementsByType(resolved, "button");
+ const closeButtons = buttons.filter((button) => button.props["aria-label"] === "Cerrar panel");
+
+ assert.equal(closeButtons.length, 1);
+
+ await closeButtons[0]?.props.onClick?.();
+
+ assert.equal(wasClosed, true);
+});
+
+test("mantiene el orden DOM esperado entre header, body y footer", () => {
+ const html = renderToStaticMarkup(
+ Footer content
} title="Context title">
+