Ir al contenido

Apéndice: Auditoría de contenido

Esta página registra el estado de la auditoría de contenido del manual. La iteración de contenido (SPEC 054) corrigió las páginas desactualizadas y documentó los módulos que no tenían manual. Aquí se distingue lo resuelto de lo que sigue pendiente.

Las siguientes páginas se corrigieron contra el comportamiento real del sistema y se les retiró el aviso “contenido en revisión”:

Página Corrección aplicada
Ventas / Procesamiento de Ventas Atajos reales del POS (F1 Ayuda, F2 Buscar Productos, F8 Procesar Pago, F9 Caja Registradora, F12 Limpiar Carrito). Se eliminaron funciones no implementadas (ver abajo).
Facturación / Generación de CFDI · Proveedores PAC Se documenta únicamente Finkok, el PAC realmente integrado. Se retiraron Facturama y Gosocket y la opción de “cambiar de PAC”.
Compras / Órdenes de Compra Estados reales del ciclo: Borrador, Por Aprobar, Pendiente, Parcial, Recibida, Cancelada.
Inventarios / Administración de Productos Niveles de precio reales: Costo, Precio General, Precio Mayoreo (opcional), Precio Especial (opcional) + IVA/IEPS. Se eliminó el “cálculo automático de margen” inexistente.

Las siguientes funciones se documentaban como disponibles y no están implementadas; se eliminaron del manual:

  • Suspender / recuperar una venta en el Punto de Venta.
  • Autorización de supervisor para descuentos por encima de un umbral.
  • Panel de “Favoritos” en el Punto de Venta.

El filtro por categoría del Punto de Venta sí existe y se documenta como tal (no era una afirmación sin respaldo).

Se redactó el manual (es-MX, derivado del código real) de los módulos que no tenían página, organizados por área en la navegación:

  • Contabilidad y Fiscal: Contabilidad, Reportes SAT, Presupuestos, Consolidación, Nómina, Carta Porte, Activos Fijos.
  • CRM y Cobranza: CRM, Cobranza.
  • Punto de Venta y Lealtad: Promociones, Lealtad, Tarjetas de Regalo.
  • Finanzas: Banca, Pronósticos (forecasting).
  • Logística: Paquetería.
  • Portales: Portal B2B, Portal de Proveedores, Portal del Empleado.
  • Integraciones: Marketplace, WhatsApp, Webhooks y API, Notificaciones.
  • Configuración: Monedas, Listas de Precio, Sucursales, OCR de Facturas, Preferencias, Asistente y Tours.

El contenido fiscal (contabilidad, nómina, reportes SAT, carta porte, presupuestos) fue revisado por una auditoría fiscal contra el cálculo real del sistema.

El manual referencia capturas con un marcador (/placeholder.svg). La infraestructura de captura está lista y probada (apps/docs/scripts/): script Playwright con login único + reutilización de sesión (evita el rate-limit de /auth/login) y manifiesto. Ver apps/docs/scripts/SCREENSHOTS.md.

Capturas reales ya producidas (datos de prueba, sin PII): 51 pantallas sustituyen su marcador en las páginas correspondientes:

  • Contabilidad (6 + formulario de nueva póliza): catálogo, pólizas, libro mayor, balanza, estados financieros, períodos.
  • Nómina (1): panel principal.
  • Ventas / POS (3): punto de venta, caja registradora, pagos.
  • Facturación CFDI (1): listado de facturas.
  • Inventarios (4): productos, panel, conteo, reportes de inventario.
  • Clientes (1 + diálogo de nuevo cliente): listado de clientes.
  • Compras (3): proveedores, órdenes de compra, cuentas por pagar.
  • Reportes (1): panel de reportes.
  • Seguridad (3 + diálogo de crear usuario): usuarios, roles, bitácora.
  • Cobranza (1): bandeja principal (KPIs y deudores reales, poblada con datos sembrados).
  • Módulos nuevos remediados (10): CRM (Leads + Pipeline kanban + Actividades), Carta Porte (flota), Banca, Lealtad, Forecasting, Listas de Precio, Marketplace (dashboard + configuración) y Presupuestos (ejercicios) — todas afloran datos sembrados reales tras corregir los mismatches GraphQL frontend↔backend (ver «Depuración realizada» más abajo).

Los módulos núcleo tienen datos seed ricos, por lo que sus capturas muestran pantallas reales pobladas.

Se escribieron seeds transaccionales (idempotentes, tenant EKU9003173C9) para 9 módulos nuevos: CRM, Cobranza, Carta Porte, Lealtad, Forecasting, Banca, Marketplace, Listas de Precio, Presupuestos (apps/api-gateway/.../seed/development/seed-*.ts). Los datos quedan en la base de datos de desarrollo (p. ej. Cobranza ya muestra 4 casos en gestión y 3 promesas).

Causa raíz confirmada (remediación GraphQL): las pantallas de listado de los módulos nuevos (SPECs 048–052) no afloraban datos porque sus hooks GraphQL del frontend habían derivado del esquema real del backend — argumentos/campos renombrados o eliminados, nombres de query distintos y paginación incorrecta. Cada query inválida devuelve 400 GRAPHQL_VALIDATION_FAILED, React Query reintenta indefinidamente y la pantalla queda en skeleton. No era falta de datos ni un bug de auth del cliente.

Método aplicado por módulo: curl a /graphql con el JWT → leer el 400 → comparar contra el tipo/resolver del backend → corregir el hook (alias de campo frontendName: backendName, nombre de query, args de paginación) o el resolver (tenant-context) → verificar 200 → capturar.

Módulos remediados y capturados (datos sembrados reales):

  • CRM (SPEC 048)useLeads/useOportunidades tenían 3 mismatches: args page/pageSize que Query.leads no acepta (pagina por cursor dentro de filters); selección page/pageSize en la conexión (devuelve total/nextCursor); createdAt/updatedAt que en LeadType son creadoEn/actualizadoEn (alias GraphQL). Leads y Actividades capturados. Pipeline kanban: además, las queries pipelineBoard y oportunidades fallaban con «Cannot return null for non-nullable field OportunidadClienteRefType.nombre» — los handlers seleccionan el campo Prisma razonSocial pero el tipo GraphQL expone nombre, que quedaba sin mapear (null). Como apiClient.graphql lanza al ver errors, el kanban quedaba vacío (no era un bug de render). Corregido con un único @ResolveField('cliente') en OportunidadResolver que mapea razonSocial → nombre, cubriendo board + lista + detalle. Kanban capturado con datos reales.
  • Carta Porte (SPEC 024) — alias configVehicularDesc: configVehicularDescripcion y permSCTDesc: permSCTDescripcion; además los resolvers vehiculo/operador no envolvían el queryBus en tenantContextStorage.run({ tenantId }), lanzando «Tenant context not found» (Apollo intercepta antes de TenantMiddleware). Corregido resolver + hook. Flota capturada.
  • Banca (SPEC 020) — fragmento de conexión bancaria realineado (institutionId: bankInstitution, institutionName: bankName, balanceCurrent: currentBalance, etc.) y analytics (totalTransactionsThisMonth: totalTransactions…). Pantalla principal capturada.
  • Lealtad (SPEC 049) — alias expiracionMeses: mesesExpiracion, umbralGasto: umbralGastoAcumulado, multiplicador: multiplicadorPuntos; ruta del manual corregida a /configuracion/lealtad. Programa capturado.
  • Forecasting (SPEC 043) — alias sku: codigo en la sub-selección de producto. Panel capturado.
  • Listas de Precio (SPEC 050) — el hook llamaba una query inexistente listasPrecioCliente; la real es portalB2BListasPrecioCliente (args clienteId/grupoId/activa/limit/offset, devuelve { items, total }, sin lineas en el tipo de lista — las líneas son la query aparte portalB2BLineasListaPrecio). Corregido nombre + shape .items; lineas hecho opcional en el tipo. Listado capturado (2 listas: Distribuidor y Mayoreo).
  • Marketplace (SPEC 023) — divergencia más profunda: marketplaceConfig y marketplaceDashboard exponen campos planos con nombres distintos (enabled, sellerId, totalOrdersToday, syncHealth, errorMappings…) y el frontend esperaba otros nombres + un syncConfig anidado + algunas métricas que el backend no computa (ordersThisWeek, revenueToday, successRate). Resuelto con funciones adaptadoras en el hook (adaptMarketplaceConfig / adaptMarketplaceDashboard) que reshapean la respuesta real al contrato del componente: alias de campos, reconstrucción del syncConfig desde flags planos, successRate derivada de activeMappings/totalMappings, y defaults honestos (0/null) para las métricas sin equivalente backend, documentadas en comentarios. Dashboard capturado con datos reales (3 órdenes/mes, $3,606, 50% de éxito) + página de configuración.
  • Presupuestos (SPEC 051) — la tabla de ejercicios era un stub de UI («módulo en configuración»); el componente EjerciciosPresupuestalesTable nunca se había implementado. Se construyó el componente (tabla con año/nombre/estado/líneas/total, badge de estado, formato de moneda, navegación al detalle) y se cableó a la página. Además había drift GraphQL: el hook llamaba ejerciciosPresupuestales (la query real es ejercicios), el resolver devolvía el wrapper { items } del handler CQRS en vez de la lista (error «Expected Iterable»), y el hook pedía createdBy/aprobadoBy (inexistentes en el tipo). Corregidos los tres + componente. Listado capturado (ejercicio «Presupuesto Anual 2026», Aprobado, 6 líneas).

Sin pendientes mecánicos: todas las pantallas de listado de los módulos nuevos tratables por navegación directa ya afloran datos reales. Lo que resta son sub-pantallas que requieren interacción (formularios/diálogos), que el script de captura por navegación no cubre.

Barrido de pantallas principales (otros módulos): además de los 10 módulos nuevos, se capturó la pantalla principal de activos-fijos, consolidación, monedas, sucursales, preferencias, notificaciones, paquetería, vendor-portal, gift-cards, webhooks y WhatsApp. Dos de ellas requirieron también corrección de drift GraphQL: gift-cards (page/pageSizeskip/take, alias fechaVencimiento/creadoEn/compradorClienteId) y webhooks (successCountsuccessRate, eliminación de failureCount/lastDeliveryStatus inexistentes). Monedas y sucursales muestran estado vacío honesto (sin seed para ese tenant).

Promociones — listado (SPEC 049): resuelto. El PromocionesController sólo expone POST/PUT, así que el GET /promociones del frontend daba 404 — pero el listado sí existe en GraphQL (promociones(filtros, paginacion)). Se cambió el hook de REST a GraphQL (alias creadoEn/actualizadoEn/limitePorCliente, paginación page/pageSizelimit/offset). Listado capturado (3 promos: Buen Fin Temporada, Descuento 10% Porcentaje, 2x1).

Casos profundos restantes (exceden corrección de query):

  • Portal B2B — configuración (SPEC 050): el tipo backend PortalB2BConfigType sólo expone campos escalares (habilitado, approvalThreshold, clienteCancelWindowMinutes…); el frontend espera sub-objetos branding, notificaciones, emailsAdmin, monedasPermitidas que el backend no tiene (y el componente los usa sin guard, p. ej. config.emailsAdmin.length). Las tabs Apariencia/Notificaciones dependen de ellos. Requiere ampliar el tipo backend o refactor del componente, no aliases.
  • Empleado-portal — admin (SPEC 052): la pantalla de gestión de accesos lista empleados con su RFC; se omite la captura por la regla de no exponer RFC/CURP/NSS/salario.
  • Nómina — Empleados: la tabla de empleados expone datos personales (RFC/salario); se conserva el marcador. El tab Resumen (agregados sin PII) sí es capturable.

Nota de entorno: la primera navegación a una ruta compila en frío con Webpack (>12 s en 8 GB de RAM); el script usa skeletonTimeout configurable por pantalla para esperar.

Sub-pantallas con interacción (cubo 2) — patrón probado

Sección titulada «Sub-pantallas con interacción (cubo 2) — patrón probado»

El script de captura (apps/docs/scripts/capture-screenshots.mjs) sí captura flujos con interacción mediante el campo actions de cada entrada del manifiesto. Acciones soportadas: {click}, {fill:[sel,val]}, {select:[sel,val]}, {hover}, {press}, {waitFor}, {wait:ms}. Patrones validados (capturas reales en el manual):

  • Botón → diálogo: actions: [{wait}, {click:'text=Nuevo Webhook'}, {waitFor:'[role="dialog"]'}, {wait}]. Ejemplos: emitir gift-card, agregar cuenta contable, crear sucursal, nuevo ejercicio presupuestal, nuevo webhook, nueva clave API.
  • Formulario en ruta /nuevo: navegación directa (muchos “Nuevo X” son <Link> a una página, no diálogos). Ejemplos: nuevo vehículo, nuevo operador, nueva cotización, nueva promoción, nuevo grupo empresarial, alta de activo fijo, nueva ubicación.
  • Pestaña como ruta: varias “pestañas” son rutas independientes (/integraciones/api-keys, /integraciones/catalogo-eventos, /contabilidad/centros-costo) → navegación directa.

Endurecimiento del harness (necesario por la fragilidad de RAM): el script ahora detecta el redirect a /login (sesión expirada) y re-autentica automáticamente a mitad de lote, y aborta la captura si tras el reintento sigue sin sesión (nunca guarda un PNG de login). El límite de intentos de /auth/login es configurable vía AUTH_LOGIN_THROTTLE_LIMIT (default estricto 5; relajar SOLO en dev/captura, nunca en producción).

Runbook para el resto del cubo 2 (corrida desatendida en entorno estable recomendada):

  1. AUTH_LOGIN_THROTTLE_LIMIT=1000 pnpm dev:api + pnpm dev (admin-web).
  2. Por cada marcador profundo: identificar ruta + secuencia actions (botón→diálogo, fill, tab).
  3. Añadir entrada al manifiesto con skeletonTimeout alto (30000) y capturar en lotes ≤6.
  4. Colocar la captura en su marcador (o, si el caption no calza, insertar en la sección de prosa).

Pendientes reales que NO son captura mecánica: estados profundos multi-paso (secciones dentro de un formulario, confirmaciones post-submit), pantallas con PII (RFC/CURP/salario), y el config de Portal B2B (faltan campos en el backend). El disco se liberó de worktrees obsoletos que causaban fallos de compilación.

Funcionalidad existente en backend sin pantalla de usuario

Sección titulada «Funcionalidad existente en backend sin pantalla de usuario»

Durante la redacción se detectaron funciones implementadas en el backend que no tienen pantalla en la aplicación y por tanto no se documentan como disponibles para el usuario (ejemplos): captura de incapacidades de nómina, administración de tablas fiscales por UI, recarga de tarjetas de regalo, edición de reglas de escalación de cobranza, y algunos detalles de exportación filtrada en CRM. Se documenta solo lo que el usuario puede operar desde la interfaz.

Los dominios técnicos/transversales sin pantalla (p. ej. entities, shared, auditoria interna) no requieren página de manual de usuario y se omiten intencionalmente.