Desviaciones intencionales — Web (App mejor que Figma)
Propósito. El objetivo de la paridad de diseño es que la app coincida con el diseño en Figma. Pero no toda diferencia es un defecto: a veces la implementación actual es deliberadamente mejor que el frame de Figma — más usable, más eficiente, o más consistente con el design system / la plataforma web. Cuando ese es el caso, conservamos la implementación y la registramos aquí con su razón, en lugar de degradarla para igualar el píxel del diseño.
Esto complementa la galería
README.md: cada entrada de aquí corresponde a la fila Desviación (propuesta) de una tabla de la galería. Una desviación aceptada convierte un ❌/⚠️ de la columna Notas en un ✅ (desviación aceptada).Regla operativa (por qué existe este archivo): antes de crear un draft de UI polish para una diferencia, revisá si ya está registrada como desviación aquí (o en la fila Desviación de la galería). Si lo está, no se abre issue — se acepta la implementación y, si corresponde, se actualiza el frame de Figma.
Cómo se decide una desviación
Section titled “Cómo se decide una desviación”Una diferencia se registra como desviación intencional (no como bug) solo si cumple al menos una de estas y ninguna regla de negocio/accesibilidad la contradice:
- Más usuario-amigable — mejora la usabilidad real (afordancia más clara, menos pasos, feedback de estado, área de click adecuada).
- Más eficiente / consistente con el design system — usa un token, un componente MUI o un patrón ya establecido en la app, evitando un valor único del frame.
- Más consistente con la plataforma web — respeta convenciones de la web / MUI (drawer sobre lista, focus ring, inputs nativos, densidad de tabla) que el frame —pensado de forma genérica— no refleja.
Requisitos de la entrada: evidencia (medida u observada, no “se ve mejor”), el archivo de código, y —si aplica— una nota para que el diseño en Figma se actualice a la implementación (para que el frame deje de ser la “verdad” divergente).
Estado
Section titled “Estado”- Abierto 2026-08-07 (a partir de la re-auditoría de esa fecha — ver galería). Veredicto de la capa de componentes: 45 ✅ · 14 ⚠️ · 0 ❌ (59 componentes) — verde. En pantallas gran parte de la remediación de 2026-07 ya cascadeó (muchas ❌→⚠️/✅); los ❌ restantes son de composición, no de token.
- 9 candidatas de desviación surgidas de la comparación Figma↔App (la última, el matiz del tinte de varianza W32, agregada por #613). Pendientes de visto bueno del diseño/PO. Se listan con la regla que cumplen, la evidencia y una recomendación.
Candidatas propuestas (pendientes de aceptación)
Section titled “Candidatas propuestas (pendientes de aceptación)”Cada una es un caso donde la app difiere del frame de forma potencialmente mejor. Decisión pendiente: aceptar (conservar la app + actualizar Figma) o rechazar (igualar el frame).
AdminShell — nav lateral reorganizado (Comercios / Tiendas / Apariencia / Terminales / Ajustes)
Section titled “AdminShell — nav lateral reorganizado (Comercios / Tiendas / Apariencia / Terminales / Ajustes)”- Capa / nodo Figma: shell ·
97:35(Side Nav) - Código:
src/components/AdminShell/adminNav.ts - Diseño dice: el grupo ADMINISTRACIÓN lista «Comercio y tiendas / Terminales / Ajustes».
- App hace: tras el overhaul (#538) el nivel comercio↔tienda se separó — «Comercios / Tiendas» — y se agregó un ítem de white-label «Apariencia»; queda «Terminales / Ajustes».
- Por qué se conserva: regla 2/3 — la IA nueva refleja el modelo de datos real (comercio y tienda son entidades distintas) y expone el editor de marca ya implementado.
- Acción en Figma: actualizar la navegación del frame
97:35a la IA nueva; deprecar «Comercio y tiendas» como un solo ítem. - Recomendación: aceptar (Figma desactualizado). · Aceptada por: (pendiente) · Fecha: —
Altas en drawer lateral sobre la lista (en vez de página completa)
Section titled “Altas en drawer lateral sobre la lista (en vez de página completa)”- Capa / nodo Figma: patrón · varios (W21/W23/W24/W38/W46b)
- Código:
src/app/admin/**(formularios de alta montados comoDialog/drawer del DS) - Diseño dice: varios frames dibujan el alta/edición como página completa con sticky save bar.
- App hace: resuelve el alta como drawer lateral derecho sobre el listado (Categoría, Impuesto, Empleado, Depósito…), preservando el contexto de la tabla.
- Por qué se conserva: regla 1/3 — patrón web común; el usuario no pierde el listado ni su scroll/filtros, y el retorno es inmediato.
- Acción en Figma: documentar el patrón drawer-sobre-lista para altas, o confirmar la página completa como canónica caso por caso.
- Recomendación: aceptar el patrón (revisar caso a caso). · Aceptada por: (pendiente) · Fecha: —
Search Field — relleno outlined blanco + hairline (en vez del soft-fill del frame)
Section titled “Search Field — relleno outlined blanco + hairline (en vez del soft-fill del frame)”- Capa / nodo Figma: atom ·
37:35 - Código:
src/ui/atoms/SearchField/index.tsx - Diseño dice: campo de búsqueda con relleno suave (fill gris
#F0F4F8). - App hace: relleno blanco + borde hairline
#E5E7EB(variante outlined), coherente con el resto de los inputs del DS (FormField,Select,Textarea). - Por qué se conserva: regla 2 — un solo tratamiento de input en toda la app; mezclar filled y outlined rompería la consistencia.
- Acción en Figma: unificar el search field al tratamiento outlined del resto de campos, o marcar el soft-fill como excepción deliberada.
- Recomendación: aceptar. · Aceptada por: (pendiente) · Fecha: —
Date-range Picker — dos campos Desde / Hasta (en vez del pill combinado con chevron)
Section titled “Date-range Picker — dos campos Desde / Hasta (en vez del pill combinado con chevron)”- Capa / nodo Figma: atom ·
103:116 - Código:
src/ui/atoms/DateRangePicker/index.tsx - Diseño dice: un solo pill combinado con chevron que abre el calendario.
- App hace: dos inputs explícitos (Desde / Hasta).
- Por qué se conserva: regla 1 — los dos campos comunican el rango y permiten teclear cada extremo sin abrir el popover.
- Acción en Figma: decidir: adoptar los dos campos en el frame, o mantener el pill y ajustar la app.
- Recomendación: a discutir. · Aceptada por: (pendiente) · Fecha: —
W01 · Iniciar sesión — toggle de visibilidad de contraseña (ojo)
Section titled “W01 · Iniciar sesión — toggle de visibilidad de contraseña (ojo)”- Capa / nodo Figma: screen ·
107:358 - Código:
src/ui/Inputs/TextInput.tsx(campo password) - Diseño dice: el campo de contraseña no muestra affordance de mostrar/ocultar.
- App hace: agrega un icono de ojo para revelar la contraseña.
- Por qué se conserva: regla 1 — affordance estándar que reduce errores de tecleo.
- Acción en Figma: agregar el icono trailing de mostrar/ocultar al campo password del frame
107:358. - Recomendación: aceptar. · Aceptada por: (pendiente) · Fecha: —
- Nota: el tamaño de campos/botón de W01 (más altos que Figma) NO es desviación — es un bug de
overrides
sx(TextInput.tsx/PrimaryButton.tsx); va a la galería como ❌ a corregir.
W30 · Detalle de venta — tabla de Artículos más completa que el frame
Section titled “W30 · Detalle de venta — tabla de Artículos más completa que el frame”- Capa / nodo Figma: screen · W30
- Código:
src/app/admin/pos/sales-orders/[id]/** - Diseño dice: desglose de artículos más escueto.
- App hace: tabla de líneas más rica (columnas y totales adicionales) manteniendo el layout de dos tarjetas y los tokens del DS.
- Por qué se conserva: regla 1/2 — más información útil en la misma composición.
- Acción en Figma: enriquecer la tabla de artículos del frame W30 para reflejar las columnas reales.
- Recomendación: aceptar. · Aceptada por: (pendiente) · Fecha: —
W54 · Apariencia (white-label) — acciones extra en el encabezado (Restablecer / Cancelar)
Section titled “W54 · Apariencia (white-label) — acciones extra en el encabezado (Restablecer / Cancelar)”- Capa / nodo Figma: screen · W54
- Código:
src/components/WhiteLabel/ThemeEditorForm.tsx - Diseño dice: el encabezado del editor solo muestra «+ Guardar».
- App hace: agrega «Restablecer estilos» (outlined) y «Cancelar» junto a Guardar.
- Por qué se conserva: regla 1 — un editor de tema necesita descartar y revertir cambios.
- Acción en Figma: agregar las acciones Restablecer/Cancelar al encabezado del frame W54.
- Recomendación: aceptar. · Aceptada por: (pendiente) · Fecha: —
Product Card — estado Agotado atenúa la tarjeta (alpha 0.6)
Section titled “Product Card — estado Agotado atenúa la tarjeta (alpha 0.6)”- Capa / nodo Figma: molecule ·
44:50 - Código:
src/ui/molecules/ProductCard/index.tsx - Diseño dice: la tarjeta agotada mantiene opacidad plena; pill roja bajo el precio.
- App hace: baja toda la tarjeta a alpha 0.6 en Agotado (y la vuelve no-interactiva).
- Por qué se conserva: regla 1 — comunica “no disponible” de un vistazo. (Espejo de la desviación ya aceptada en móvil.)
- Acción en Figma: añadir un estado OutOfStock a 60% de opacidad a la variante del componente.
- Recomendación: aceptar. · Aceptada por: (pendiente) · Fecha: —
VAS — navegación por sidebar (sin barra de pestañas en la página) — W41
Section titled “VAS — navegación por sidebar (sin barra de pestañas en la página) — W41”- Capa / nodo Figma: screen ·
127:6816(Pago de servicios) - Código:
src/components/Vas/VasSaleScreen.tsx+ rutassrc/app/admin/{service-payments,airtime,giftcards,transfers,deposits} - Diseño dice: cada pantalla VAS lleva una barra de pestañas en la página (Servicios / Tiempo aire / Pines / Transferir / Depósitos / Wallet) para saltar entre servicios.
- App hace: los servicios VAS son rutas propias navegables desde el sidebar de admin (grupo Finanzas); no se duplica una barra de pestañas dentro de la página. La CTA está unificada en “Continuar” en todo VAS (ver #548), en vez del “Agregar a la venta” del frame.
- Por qué se conserva: regla 3 — la IA del admin web usa el sidebar como navegación primaria; una barra de pestañas in-page duplicaría esa navegación. La copia “Continuar” es consistente en todos los flujos VAS.
- Acción en Figma: decidir — adoptar la navegación por sidebar en los frames VAS (quitar la barra de pestañas y alinear la CTA a “Continuar”), o confirmar la barra in-page como canónica y re-alinear la app.
- Recomendación: a discutir (probable Figma desactualizado). · Aceptada por: (pendiente) · Fecha: —
VAS TAE — flujo por pasos en vez de página compuesta — W42
Section titled “VAS TAE — flujo por pasos en vez de página compuesta — W42”- Capa / nodo Figma: screen ·
127:7043(Tiempo Aire) - Código:
src/components/Vas/VasSaleScreen.tsx(kind="AIRTIME"), rutasrc/app/admin/airtime - Diseño dice: una página compuesta — Compañía (chips) + grilla de Monto + panel de Captura (teléfono/total) en una sola vista.
- App hace: un flujo por pasos (paso 1: elegir proveedor → paso 2: monto/captura), en línea con el resto de los flujos VAS de la app.
- Por qué se conserva: regla 1 — el flujo por pasos reduce la carga visual en pantalla táctil y reutiliza el patrón VAS compartido; el frame compuesto es un layout genérico.
- Acción en Figma: documentar el flujo por pasos en el frame
127:7043, o confirmar la página compuesta como objetivo (implicaría rediseñar elVasSaleScreen). - Recomendación: a discutir. · Aceptada por: (pendiente) · Fecha: —
Turnos — matiz del tinte de varianza (faltante en rojo vs ámbar del frame) — W32
Section titled “Turnos — matiz del tinte de varianza (faltante en rojo vs ámbar del frame) — W32”- Capa / nodo Figma: screen ·
119:2117(Turnos, columna “Dif.”) - Código:
src/components/PosShifts/ShiftList.tsx(columnavariance) +ShiftDetail.tsx(varianceStatusVariant) - Diseño dice: el frame dibuja la varianza negativa (faltante,
−$20) como píldora ámbar; el resto de la columna es texto plano. - App hace: #613 restauró el
StatusBadgeen la lista para tintar sólo la excepción (faltante/discrepancia), pero mapea negativo →danger(rojo) reutilizandovarianceStatusVariant, la fuente única de la semántica de varianza que el detalle W33 ya renderiza (rojo = faltante, issue #51/#464). - Por qué se conserva: regla 2 — un solo helper gobierna el color de varianza en lista y detalle; forzar ámbar sólo en la lista rompería esa consistencia y duplicaría la regla. El realce estructural (píldora en la excepción, texto plano en lo normal) ya coincide con el frame; sólo difiere el matiz.
- Acción en Figma: alinear el frame
119:2117al rojo semántico de faltante (consistente con W33), o confirmar el ámbar como canónico y ajustar el helper compartido en un solo lugar. - Recomendación: a discutir (probable matiz de frame). · Aceptada por: (pendiente) · Fecha: —
Desviaciones aceptadas
Section titled “Desviaciones aceptadas”FAB de navegación en /terminal/home (speed-dial reubicado fuera del header) — ✅ ACEPTADA
Section titled “FAB de navegación en /terminal/home (speed-dial reubicado fuera del header) — ✅ ACEPTADA”- Capa / nodo Figma: screen · W10 (
255:3399) - Código:
src/app/terminal/home/**(DSFabspeed-dial; versrc/ui/atoms/Fab/index.tsx) - Diseño dice: el frame W10 no incluye un FAB; las acciones de turno viven en otra parte del layout.
- App hace: un FAB speed-dial abajo-izquierda que hospeda Movimiento de caja / Cerrar turno / Anular venta, visible solo con turno abierto — reubicado deliberadamente fuera del top bar del kiosco.
- Por qué se conserva: regla 1/3 — acceso rápido a las acciones de turno sin recargar el header del POS; patrón táctil idiomático. Decisión de producto explícita: el FAB se mantiene.
- Acción en Figma: documentar el FAB de navegación en el frame W10 (
255:3399). - Aceptada por: usuario (PO). · Fecha: 2026-08-08.
Consola de liquidaciones (ops) — pantalla sin frame de Figma — ✅ ACEPTADA
Section titled “Consola de liquidaciones (ops) — pantalla sin frame de Figma — ✅ ACEPTADA”- Capa / nodo Figma: ninguno — pantalla construida sin frame (herramienta interna).
- Código:
src/components/Settlements/SettlementsOpsConsole.tsx· rutasrc/app/admin/settlements/ops/page.tsx(gap #55, issue #575). - Diseño dice: no existe frame — la consola de operaciones nunca se diseñó en Figma.
- App hace: arma la pantalla a partir de patrones del DS (el mismo compuesto lista-con-acciones
que
DepositList:PageHeader+Tabs+DataTable+Dialog/ConfirmDialog+ toasts), con dos colas — Liquidaciones pendientes (Marcar liquidada capturando la clave de rastreo SPEI por destino / Rechazar con motivo) y Cuentas bancarias por verificar (ver carátula, Verificar / Rechazar). Es una superficie platform-admin-only (404 para el resto). - Por qué se conserva: regla 2 — reutiliza componentes y tokens ya establecidos, sin inventar primitivos ni un layout único; es una herramienta interna de operaciones, no una pantalla de cliente, por lo que no entra a la galería de paridad ni lleva frame de Figma.
- Acción en Figma: ninguna — no se crea frame (decisión de producto). Si a futuro se diseña, se parte de esta implementación.
- Aceptada por: usuario (PO), decisión de gap #55. · Fecha: 2026-08-09.
W35 «Cerrar turno» — el desglose por denominación (W65) sustituye el resumen de dos tarjetas — ✅ ACEPTADA (gap #27)
Section titled “W35 «Cerrar turno» — el desglose por denominación (W65) sustituye el resumen de dos tarjetas — ✅ ACEPTADA (gap #27)”- Capa / nodo Figma: screen · W35
132:9670→ sucesor W65574:14732 - Código:
src/components/ShiftClose/ShiftCloseView.tsx(W65, grid de denominación) +ShiftCloseSummary.tsx/ShiftCloseSummaryStep.tsx(W35, paso de resumen). - Diseño dice: el frame W35 (
132:9670) dibuja el cierre como dos tarjetas de resumen (“Conteo de efectivo” + “Ventas por método”) más “Imprimir corte (Z)” / “Cerrar turno”. - App hace: la superficie de conteo enviada (
/admin/pos/shifts/close→ShiftCloseView) es el desglose por denominación del frame W65 (574:14732): 3KpiStatCard+ unaDenominationRowpor denominación + total contado / esperado / diferencia. El resumen W35 (ShiftCloseSummary) queda como el paso posterior al conteo (#587), no como la pantalla de conteo. - Por qué se conserva: gap #27 — decisión de producto registrada (2026-07-06): «W65 extiende/sustituye W35». Contar billete a billete es más preciso para el arqueo de caja que capturar un total, así que W65 es el sucesor canónico de W35, no un defecto de composición. Regla 1/2.
- Acción en Figma: marcar
132:9670(W35) como superseded-by574:14732(W65); conservar el resumen de dos tarjetas como el paso de confirmación posterior al conteo. - Evidencia de paridad:
docs/ui-evidence/671/— el slotw35-cerrar-turnose recapturó contraShiftCloseSummary(coincide con132:9670) yw65-cerrar-turno-desglosecontraShiftCloseView(coincide con574:14732), reconciliando el veredicto best-effort de #669/#670, que había capturado el grid W65 bajo el nombre de W35. Por gap #27 el “mismatch” W35↔grid no se re-abre como issue. - Aceptada por: decisión de producto, gap #27. · Fecha: 2026-07-06.
W65 en la terminal · Cerrar turno (Corte Z) — bandejas Billetes/Monedas + resumen fijo al pie
Section titled “W65 en la terminal · Cerrar turno (Corte Z) — bandejas Billetes/Monedas + resumen fijo al pie”- Capa / nodo Figma: screen · W65
574:14732(terminal: diálogo del FAB de turno en/terminal/home) - Código:
src/app/terminal/shift/close/ShiftCloseView.tsx+CloseReconciliationBar.tsx(el W65 de admin,src/components/ShiftClose/ShiftCloseView.tsx, no cambia de layout). - Diseño dice: título «Cerrar turno · Corte Z», 3 KPI (Ventas brutas / Efectivo esperado / Pagos con tarjeta), una rejilla de 2 columnas con las 11 denominaciones mezcladas y los totales + botón «Cerrar turno (Z-Report)» al final de la tarjeta.
- App hace: (1) un solo título — la barra del diálogo ya dice «Cerrar turno», la vista sólo agrega la
línea «Corte Z · Turno abierto desde …»; (2) KPI sólo con cifras reales: el snapshot de cierre del cajero
no trae ventas (api
getCloseSnapshot), así que se muestran «Fondo inicial» + «Efectivo esperado» y las tarjetas de ventas aparecen sólo si el api las envía; (3) el conteo en dos bandejas Billetes ($1,000–$20) y Monedas ($10–$0.50) con subtotal por bandeja; (4) los totales, la Diferencia con su estado (Sin contar / Cuadra / Faltante / Sobrante), la línea de autorización de supervisor y el botón «Cerrar turno» viven en una barra fija al pie (geometría deStickySaveBar, colores deStatusBadge), siempre visible mientras se cuenta. - Por qué se conserva: regla 1 — en la terminal el botón y la diferencia quedaban bajo el pliegue y
las tarjetas de ventas sólo podían mostrar «—». Es la variante «B — Panel fijo» del canvas
pos-shift-actions, llevada al pie para que funcione igual a 768 px. - Acción en Figma: derivar un frame terminal de W65 con las bandejas y el resumen fijo; quitar «(Z-Report)» del botón.
- Recomendación: aceptar. · Aceptada por: (pendiente) · Fecha: —
/invitacion — página pública de aceptación de invitación sin frame de Figma — ✅ ACEPTADA (gap #55)
Section titled “/invitacion — página pública de aceptación de invitación sin frame de Figma — ✅ ACEPTADA (gap #55)”- Capa / nodo Figma: screen · no existe frame — la landing de aceptación nunca se diseñó en Figma
(MerchantInvitations E5, spec
docs/integrations/verification/merchant-invitations-completion-design.md). - Código:
src/app/invitacion/(page.tsx,AcceptView.tsx,invitacion.hooks.ts,invitacion.i18n.ts). - Diseño dice: no existe frame — la página pública de aceptación no tiene artboard.
- App hace: arma la pantalla a partir de patrones del DS (
Paper/tarjeta centrada +Bannerde éxito/peligro +Button), con la máquina de estados de la spec §6.2: sin tuple válido → estado “invitación venció o no es válida” (falla cerrada); sin sesión → CTAs “Iniciar sesión” / “Crear mi cuenta” que preservan la tuple{teamId, membershipId, userId, secret}al volver a/invitacion; con sesión →POST /pos/invites/accept(mock-first) → pantalla de éxito con CTA persistente “Crea tu PIN para vender” (deep-link a la terminal, E6). es-MX, sin nombres de proveedor. - Por qué se conserva: regla 2 — reutiliza componentes y tokens del DS sin inventar primitivos ni un layout único; es una landing de flujo completada pre-prod (misma disciplina no-flag / es-MX / sin marca de la ola MerchantVerification). gap #55 (ruta canvas/patrón-DS, no Figma).
- Acción en Figma: ninguna — no se crea frame por ahora; coordinar con terminales-design si se quiere un artboard W-invitacion dedicado. Si a futuro se diseña, se parte de esta implementación.
- Aceptada por: tl-verification, decisión de gap #55. · Fecha: 2026-09-03.
- Seguimiento (batch
8ac7050bee, #712): el ida-y-vuelta post-auth quedó cableado de punta a punta —/loginy/signupahora consumen elreturnTo(mismo concepto que elredirectde los guards de sesión) validado a ruta interna del mismo origen (anti open-redirect, OWASP A01) víasrc/core/auth/postAuthRedirect.ts, y regresan a/invitacioncon la tuple intacta tras iniciar sesión / crear cuenta (incluye las rutas MFA y magic-link). Sin cambio visual: reutiliza los frames de Figma existentes de/loginy/signup.
W39 · Invitar usuario — estados de resultado en-diálogo (éxito / «correo ya invitado o registrado») sin frame — ✅ ACEPTADA (gap #19)
Section titled “W39 · Invitar usuario — estados de resultado en-diálogo (éxito / «correo ya invitado o registrado») sin frame — ✅ ACEPTADA (gap #19)”- Capa / nodo Figma: dialog · W39
131:8930(existe el frame por defecto del formulario; faltan los frames de éxito y de error, gap #19 / draftweb-parity-staff-roles-invite.mdFD-7). - Código:
src/components/Users/InviteUserDialog.tsx(panel de resultado en-diálogo); showcasesrc/app/playground/showcases/PersonalInvitarScreenShowcase.tsx(tres frames lado a lado). - Diseño dice: el frame W39 sólo dibuja el formulario; el resultado de enviar la invitación no tiene artboard (antes era sólo un toast).
- App hace: al enviar, el diálogo reemplaza el formulario por un panel de resultado armado con
patrones del DS (
IconCircleCheck/TriangleExclamation+ título + cuerpo): éxito («Invitación enviada» + acción “Invitar a otra persona” / “Listo”) y error («Ese correo ya tiene acceso» cuando el backend responde 409 / ya-miembro, con acción “Volver” para corregir y reintentar). Se conserva el toast por compatibilidad. es-MX, sin nombres de proveedor. - Por qué se conserva: regla 2 — completa un flujo pre-prod reutilizando componentes/tokens del DS sin inventar primitivos; misma disciplina no-flag / es-MX / sin marca de la ola MerchantInvitations.
- Acción en Figma: coordinar con terminales-design el frame de éxito/error de W39 (gap #19 / #55). Si a futuro se diseña, se parte de esta implementación.
- Aceptada por: tl-verification, decisión de gap #19. · Fecha: 2026-09-03.
Para aceptar una candidata: movela desde «Candidatas propuestas» a esta sección con quién la aceptó y
la fecha, poné su celda Desviación de la galería como 🔶 Aceptada, y actualizá el frame de Figma.