Saltar al contenido
Payment Emulator LabDocumentaciónEntrar a la consola

Interfaces

La consola del operador

apps/console es la aplicación Nuxt en la que entra quien opera. Registraaplicaciones, rota credenciales, lee el libro mayor, libera y retira dinero,administra bancos y personas, e

10 min de lectura

Índice / Index · English

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, cablea aria-describedby y decide cómo toma el control su nombre. Es el fichero que conoce las trampas, y son tres:

    • Nombrar. InputText, Password, Textarea y ToggleSwitch montan un <input> real, así que <label for> los nombra. Select dibuja un <span role="combobox">, al que un for no 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. InputNumber no tiene inputProps, así que un aria-describedby escrito en el componente aterriza en el span que lo envuelve y el input de dentro se queda sin nombrar. Se llega a él con :pt="{ pcInputText: { root: { 'aria-describedby': field.describedBy } } }". InputChips sí acepta inputId e inputProps, así que le vale una etiqueta normal.
    • Altura. Ver abajo.
  • Una altura, un tamaño de letra. Todo control de una línea dentro de un campo mide --control-h y va a 14 px, y eso lo afirma RField. PrimeVue no se los da iguales: un Select sale de 42 px contra los 39 de un InputText, 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 que aria-describedby ha 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.mjs es 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. Dibuja fieldset+legend cuando tiene nombre y un div cuando no, para que un grupo sin nombre no anuncie una leyenda vacía. min-column gobierna 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 dice sortable tiene que nombrar un campo que el endpoint declaró ordenable, o la API responde unsortable_field en 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 leen core/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 responde 403 y 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 un Card.
  • RTour — el anfitrión del tour guiado. Los pasos se registran por página con useHelpTour().register(...); un paso cuyo objetivo no está en la página se descarta en vez de apuntar a la nada.

#Reacciones y fallos

  • useNotifysucceeded / failed, sobre el Toast de PrimeVue.
  • useConfirmActiondestructive(...), sobre ConfirmDialog. Nada en la consola usa window.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 usa useRequestFetch, porque $fetch no reenvía la cookie de sesión entrante y un listado renderizado en servidor lo respondería con 401 nuestro 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 con primevue.importTheme y no con options.theme: un preset escrito como cadena pasa por la serialización de runtimeConfig de Nuxt, PrimeVue emite bloques de variables vacíos, y cada var(--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. darkModeSelector es .dark sobre <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

  1. modules/<entidad>/<entidad>.model.ts — la interfaz y createRestGateway({ resource: '/api/<entidad>' }).
  2. modules/<entidad>/<entidad>.columns.ts — las columnas, acordes con lo que el endpoint declaró ordenable y filtrable.
  3. server/api/<entidad>/… — un fichero por verbo, proxyList para el listado.
  4. pages/<entidad>.vueRPageHeader, useEntityCollection, RTable, y un formulario hecho con RFormSection + RField.
  5. core/navigation.ts — la entrada, con roles cuando un rol no pueda abrirla.
  6. 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.

Payment Emulator Lab · RonuSoftwareMIT