Traducción de
docs/en/frontend/console.md, que es el original. Si discrepan, manda el inglés.
apps/console es la aplicación Nuxt en la que entra quien opera. Registra
aplicaciones, rota credenciales, lee el libro mayor, libera y retira dinero,
administra bancos y personas, e inspecciona trazas y entregas de webhook.
No habla con nada más que con su propio servidor Nitro. Nitro habla con la API.
Ésa es la regla alrededor de la que está ordenada la aplicación entera: una
credencial se para en el servidor, y una página que quiere datos se los pide a
un gateway que sólo conoce /api/….
#Sus capas
| Capa | Contiene | Regla |
|---|---|---|
app/core/ |
El runtime compartido: gateways, la tubería de listados, primitivas de disposición y de formulario, formato | Escrito una vez. Un módulo que reimplemente algo de aquí ha bifurcado el sistema de diseño |
app/modules/<entidad>/ |
El modelo, las columnas, los formularios y los componentes de una entidad | Conoce su dominio y nada del de nadie más |
app/pages/ |
Una pantalla cada una, montando core y un módulo |
No tiene lógica de carga propia |
server/api/ |
Nitro, un fichero por verbo | Lo único que puede llegar a la API del emulador |
La consola del operador está en español. La landing pública admite español e inglés. El código, los comentarios y los documentos originales, en inglés.
#Home pública y acceso
/ es la landing pública de Payment Emulator Lab. /applications conserva el
listado de aplicaciones autenticado. /login sigue siendo la página de acceso;
tras entrar lleva a /applications por defecto y conserva next para enlaces
privados. Las rutas Nitro del plano de control siguen exigiendo sesión.
La landing vive en app/modules/public/landing/components/. Su catálogo ES/EN
tipado está en app/modules/public/landing/i18n/catalog.ts, montado bajo
public.landing por Nuxt I18n. useAppLocale() es la frontera de idioma de la
aplicación; Nuxt I18n posee la cookie emulator_locale, legible durante SSR.
publicLocalePath() genera enlaces públicos localizados y alternates SEO. Véase
la arquitectura i18n. La cookie existente de useColorScheme()
(emulator_scheme, oscura por defecto) y html.dark controlan ambas paletas.
El CSS de la landing tiene su propio ámbito y no cambia la paleta del operador.
Three.js sólo carga cuando la escena decorativa entra en el viewport. Limita el DPR a 1,5 y el renderizado a 30 fps, pausa fuera de pantalla y en pestañas ocultas, muestra una composición estática con movimiento reducido y libera los recursos al desmontarse. Una tarjeta renderizada por SSR permanece visible sin JavaScript o WebGL. Los paneles de escenarios y trazas son ejemplos ilustrativos y no llaman a la API.
Los enlaces abren el portal local de documentación, construido
desde las guías Markdown bilingües. No se necesita acceso a GitHub.
NUXT_PUBLIC_SITE_URL está vacío
por defecto; configúralo con el origen público desplegado y todas las direcciones
absolutas que emite el sitio —canónica, alternativas hreflang, imagen de Open
Graph, robots.txt y sitemap.xml— lo siguen. Vacío, cada petición responde por
el host por el que llegó. No se supone un dominio de producción en instalaciones
locales.
#Direcciones públicas
La landing tiene una dirección pública por idioma: / en
español y /en en inglés. La ruta decide el idioma, así que cada versión se
puede enlazar e indexar por separado; la cookie emulator_locale sólo recuerda
cuál eligió quien lee. Ambas se canonizan a sí mismas y declaran a la otra con
hreflang. /sitemap.xml lista ambas landings y las guías publicadas bajo
/docs/es y /docs/en. Todo lo que hay detrás del login responde con
X-Robots-Tag: noindex.
Ejecuta npm run dev:console desde la raíz para ver la landing en el puerto
3000. Se renderiza sin API ni base de datos. El login y la consola autenticada
necesitan la API y la contraseña de sesión configuradas, como antes.
Con la consola en marcha y NUXT_SESSION_PASSWORD configurada, ejecuta
npm run smoke:landing. Comprueba el SSR público en ambos idiomas y temas, los
enlaces al login, las redirecciones privadas y el rechazo anónimo de Nitro sin
necesitar API ni base de datos. Usa CONSOLE_URL si la consola tiene otro origen.
#core/ — el sistema de diseño
Hay exactamente un sistema de diseño: PrimeVue con el preset Aura, afinado en
app/theme.ts. Una segunda librería de componentes, un segundo juego de iconos o
una reimplementación a mano de algo que PrimeVue ya hace son defectos. Los
envoltorios de abajo existen para que lo correcto sea lo fácil, no para esconder
PrimeVue.
#Formularios
-
RField— un campo: su etiqueta, su control, su ayuda y su error. Acuña el identificador, cableaaria-describedbyy decide cómo toma el control su nombre. Es el fichero que conoce las trampas, y son tres:- Nombrar.
InputText,Password,TextareayToggleSwitchmontan un<input>real, así que<label for>los nombra.Selectdibuja un<span role="combobox">, al que unforno puede nombrar — esos campos pasan:labelable="false"y:aria-labelledby="field.labelledBy". Un par etiqueta/control escrito a mano sólo parece asociado. - Componentes que se tragan atributos.
InputNumberno tieneinputProps, así que unaria-describedbyescrito en el componente aterriza en elspanque lo envuelve y el input de dentro se queda sin nombrar. Se llega a él con:pt="{ pcInputText: { root: { 'aria-describedby': field.describedBy } } }".InputChipssí aceptainputIdeinputProps, así que le vale una etiqueta normal. - Altura. Ver abajo.
- Nombrar.
-
Una altura, un tamaño de letra. Todo control de una línea dentro de un campo mide
--control-hy va a 14 px, y eso lo afirmaRField. PrimeVue no se los da iguales: unSelectsale de 42 px contra los 39 de unInputText, un<input>sin tamaño propio hereda los 16 px del navegador mientras el panel va a 14, y un grupo con una muestra de color es más alto que los dos. Sin tocarlo, una fila de campos no es una fila. -
La ayuda es un tooltip en la línea de la etiqueta, no un párrafo bajo el campo. Una
?junto a la etiqueta, que abre al pasar por encima y al enfocar. Eso es lo que hace que dos campos contiguos midan lo mismo. No se pierde nada para un lector de pantalla: el texto sigue en el documento, oculto, con el identificador al quearia-describedbyha apuntado siempre. El error se queda en el flujo — quien acaba de ver su formulario rechazado no debería tener que buscar el motivo..tmp/fields-audit.mjses la comprobación: abre todos los formularios, agrupa los campos por la fila en la que están, y falla cualquier fila cuyos controles no midan lo mismo o cualquier ayuda cuyo texto no referencie ningún control. -
RFormSection— un grupo de campos. Dibujafieldset+legendcuando tiene nombre y undivcuando no, para que un grupo sin nombre no anuncie una leyenda vacía.min-columngobierna la rejilla adaptable. -
RFormActions— la fila de botones, para que enviar y cancelar estén igual en todas las pantallas. -
useValidation(form, rules)— espeja lo que los DTO de la API ya declaran. A propósito no es una segunda fuente de la verdad: el servidor sigue rechazando lo que rechaza; esto sólo decide cuándo se entera quien opera. Los errores no aparecen hasta salir del campo o enviar, porque un formulario que se pone rojo mientras escribes el primer carácter es un formulario discutiendo contigo. Una regla es(value, form) => string | null, así que una regla entre dos campos es una función en línea, no una característica del framework.
#Listados
useEntityCollection— la tubería entera para una entidad: parámetros, carga, paginación, ordenación, filtrado, búsqueda, borrado, y sincronía opcional con la URL para que una vista filtrada sea un enlace. Ninguna página monta una petición a mano.RTable— la tabla, más un selector de columnas, un esqueleto de carga y dos vacíos distintos: un listado que nadie ha llenado todavía, y un filtro que no casó con nada. No son el mismo mensaje.<módulo>.columns.ts— cada módulo declara sus columnas. Una columna que dicesortabletiene que nombrar un campo que el endpoint declaró ordenable, o la API respondeunsortable_fielden vez de ignorarlo en silencio. Ver el contrato de listados.
#Disposición y armazón
AppSidebar/AppTopbar— el armazón. La barra lateral se pliega a iconos con tooltips; las dos leencore/navigation.ts.navigation.ts— la navegación agrupada y filtrada por rol. Esconder una entrada que un rol no puede abrir es cortesía; la protección es que la API responde403y el middleware de ruta se niega. Pero un panel que ofrece páginas que no puedes abrir es un panel que parece roto.RPageHeader— título, una línea que dice para qué es la pantalla, y la acción principal.RStat— una baldosa densa con una cifra, deliberadamente no unCard.RTour— el anfitrión del tour guiado. Los pasos se registran por página conuseHelpTour().register(...); un paso cuyo objetivo no está en la página se descarta en vez de apuntar a la nada.
#Reacciones y fallos
useNotify—succeeded/failed, sobre el Toast de PrimeVue.useConfirmAction—destructive(...), sobre ConfirmDialog. Nada en la consola usawindow.confirm, y ninguna acción destructiva es un solo clic.REmptyState— icono, título, una línea de por qué, y la salida.useAppError().explain(error, localizedFallback)— mapea códigos/status REST semánticos a copia localizada segura. Los errores inline usan presentación computed y las notificaciones persistentes usan getters para actualizar mensajes existentes al cambiar idioma, sin repetir peticiones ni exponer texto del backend.
#Acceso a datos
createRestGateway({ resource })— los verbos de un recurso, construidos desde su ruta. Toda ruta es de Nitro en esta aplicación, nunca de la API del emulador. En renderizado de servidor usauseRequestFetch, porque$fetchno reenvía la cookie de sesión entrante y un listado renderizado en servidor lo respondería con401nuestro propio servidor.core/data/contracts/— la forma compartida de los parámetros de listado, y el serializador que la mapea a la consulta que lee la API. Declarada una vez; un listado nuevo hereda el formato en vez de inventarse uno.
#Aspecto
app/theme.ts— el preset Aura, afinado. Se carga conprimevue.importThemey no conoptions.theme: un preset escrito como cadena pasa por la serialización deruntimeConfigde Nuxt, PrimeVue emite bloques de variables vacíos, y cadavar(--p-*)del panel no resuelve a nada. El síntoma es una aplicación completamente sin estilos, y no se nota en la configuración que algo vaya mal.useColorScheme— claro y oscuro, guardado en una cookie para que el servidor renderice el mismo esquema que el navegador va a enseñar.darkModeSelectores.darksobre<html>.assets/css/console.css— los tokens: escala de espaciado, radios, ancho de contenido, altura de control, y colores de superficie y texto para los dos esquemas. Un relleno, un radio o un color arbitrarios en un componente son un defecto; el token existe.
#Añadir una pantalla
modules/<entidad>/<entidad>.model.ts— la interfaz ycreateRestGateway({ resource: '/api/<entidad>' }).modules/<entidad>/<entidad>.columns.ts— las columnas, acordes con lo que el endpoint declaró ordenable y filtrable.server/api/<entidad>/…— un fichero por verbo,proxyListpara el listado.pages/<entidad>.vue—RPageHeader,useEntityCollection,RTable, y un formulario hecho conRFormSection+RField.core/navigation.ts— la entrada, conrolescuando un rol no pueda abrirla.- Un tour, con
useHelpTour().register(listViewTour({ … })).
#Verificar un cambio
Renderízalo. npm run typecheck --workspace @payment-emulator/console caza tipos
y nada de si el panel se ve bien; hacer grep sobre el HTML servido caza aún
menos. Entra, captura la pantalla en los dos esquemas de color y ejercita el
camino de escritura — los fallos que esta consola ha tenido de verdad (un tema sin
estilos, una etiqueta apuntando a un span, un grupo de entrada solapando su
botón, una página en blanco para un registro que no existe) eran todos invisibles
a cualquier comprobación que no fuera mirar.