Appearance
Arquitectura Frontend - PWA
Resumen
Frontend PWA independiente y separado del ERP administrativo, optimizado para clientes móviles. Build estático servido por Apache (sin Node.js en producción).
Stack Técnico
Decisiones Tecnológicas
Tecnologías principales:
- Vite: Build tool y dev server
- React 19: Framework UI
- TypeScript: Type safety
- Tailwind CSS 4: Styling
- React Router 7: Navegación SPA
- Axios: Cliente HTTP
- Zustand: Estado global ligero
- Zod: Validación de esquemas
¿Por qué Vite?
- Build estático servido por Apache (sin Node.js en producción)
- Extremadamente rápido en desarrollo (HMR instantáneo)
- Build optimizado y ligero automáticamente
- PWA con plugin oficial
- TypeScript nativo
- Configuración simple
¿Por qué React 19?
- Componentes modernos con hooks
- Ecosistema maduro
- Fácil para equipo PHP que migra a frontend moderno
- Server Components y Suspense para optimización
¿Por qué Tailwind CSS?
- Rápido para prototipar
- No require CSS personalizado
- Responsive por defecto
- Pequeño bundle size con purge
¿Por qué NO Next.js?
- No se necesita SSR (es una PWA)
- No se necesita servidor Node.js en producción
- Vite es más simple y rápido para SPA
Arquitectura de Componentes
┌─────────────────────────────────────────────────────────┐
│ APP SHELL (Layout) │
│ - Header (logo empresa, nombre cliente) │
│ - Navigation (Dashboard, Deudas, Pagar, Historial) │
│ - Footer │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────┴────────────────┐
│ │
┌───────▼────────┐ ┌─────────▼────────┐
│ PAGES │ │ CONTEXTS │
│ - /login │◄─────────────┤ - AuthContext │
│ - /dashboard │ │ - BrandingCtx │
│ - /deudas │ │ - TenantContext │
│ - /pagar │ └──────────────────┘
│ - /cupones │ │
│ - /historial │ │
└───────┬────────┘ │
│ │
└────────────────┬────────────────┘
│
┌────────────────▼────────────────┐
│ │
┌───────▼─────────┐ ┌──────────▼────────┐
│ COMPONENTS │ │ API CLIENT │
│ - DeudaCard │◄───────────┤ - auth │
│ - DeudaList │ │ - ctacte │
│ - PayButton │ │ - pagos │
│ - CuponPDF │ │ - cupones │
│ - CodigoBarra │ └───────────────────┘
└─────────────────┘Capas:
- App Shell: Layout persistente con header/nav/footer
- Pages: Rutas principales (login, dashboard, deudas, etc.)
- Contexts: Estado global compartido (auth, branding, tenant)
- Components: Componentes reutilizables de UI
- API Client: Capa de comunicación con backend
Estructura de Proyecto
portal-clientes/
├── src/
│ ├── main.tsx # Entry point
│ ├── App.tsx # Router + layout
│ ├── pages/ # Páginas principales
│ │ ├── LoginPage.tsx
│ │ ├── DashboardPage.tsx
│ │ ├── DeudasPage.tsx
│ │ ├── PagarPage.tsx
│ │ ├── CuponesPage.tsx
│ │ └── HistorialPagosPage.tsx
│ ├── components/ # Componentes reutilizables
│ │ ├── layout/ # Header, Footer, Navigation
│ │ ├── deudas/ # DeudaCard, DeudaList
│ │ ├── pagos/ # PaymentButton, SelectFacturas
│ │ ├── cupones/ # CuponPDF, CodigoBarrasITF
│ │ └── ui/ # Button, Card, Input, Modal
│ ├── lib/
│ │ ├── api/ # Cliente API (axios)
│ │ ├── hooks/ # Custom hooks (useAuth, useDeudas)
│ │ ├── contexts/ # React Contexts
│ │ └── utils/ # Formateo, validación
│ └── types/ # TypeScript types
└── public/
├── icons/ # Iconos PWA
└── manifest.json # PWA manifestContextos (Estado Global)
AuthContext
Propósito: Gestionar autenticación del cliente en toda la app.
Estado:
cliente: Datos del cliente logueado (nombre, ID, email)isAuthenticated: Si está autenticadoisLoading: Cargando estado inicial
Acciones:
login(identifier, type): Identificar clientelogout(): Cerrar sesión
BrandingContext
Propósito: Aplicar branding dinámico según el tenant (colores, logo, nombre).
Estado:
appName: Nombre de la aplicaciónlogo: URL del logoprimaryColor: Color principalthemeColor: Color del tema
Uso: Se obtiene del backend al cargar el dominio, se aplica con CSS variables.
TenantContext
Propósito: Información del tenant actual (empresa) según el dominio.
Estado:
tenantId: ID del tenantdomain: Dominio usadodatabase: Base de datos del tenant
Páginas Principales
1. /login
Propósito: Identificación del cliente sin contraseña (DNI/CUIT/ID).
UI:
┌──────────────────────────┐
│ [Logo Empresa] │
│ Portal de Clientes │
│ │
│ Tipo: ( ) DNI ( ) CUIT │
│ ( ) Cliente ID │
│ │
│ Número: [___________] │
│ │
│ [ INGRESAR ] │
└──────────────────────────┘Flujo:
- Usuario selecciona tipo de identificación
- Ingresa número
- Submit → API valida
- Si OK → Redirect a /dashboard
- Si error → Muestra mensaje
API: POST /portal/auth/identify-client
2. /dashboard
Propósito: Resumen rápido del estado de cuenta.
UI:
┌──────────────────────────┐
│ Hola, Juan Pérez │
│ │
│ Saldo Total: $15,000.00 │
│ │
│ Facturas Vencidas: 3 │
│ Próximas a Vencer: 2 │
│ │
│ Último Pago: 15/01/26 │
│ │
│ [ Pagar Deudas ] │
│ [ Generar Cupón ] │
│ [ Ver Historial ] │
└──────────────────────────┘Datos mostrados:
- Nombre del cliente
- Saldo total pendiente
- Cantidad de facturas vencidas
- Cantidad de facturas próximas a vencer
- Último pago realizado
API: GET /portal/mi-cuenta
3. /deudas
Propósito: Listar todas las facturas pendientes del cliente.
UI: Cards o tabla con facturas pendientes, agrupadas por estado (vencidas/pendientes).
Datos por factura:
- Tipo y número (ej: "Factura A - 123")
- Fecha de emisión
- Fecha de vencimiento
- Monto pendiente
- Estado visual (VENCIDA en rojo, Pendiente en amarillo)
- Días de vencimiento (si aplica)
Acciones:
- Seleccionar facturas para pagar
- Botón "Pagar Seleccionadas"
API: GET /portal/deudas
4. /pagar
Propósito: Seleccionar facturas y pagar online.
UI:
┌──────────────────────────┐
│ Seleccione facturas: │
│ [✓] Factura A $10,000 │
│ [✓] Factura B $5,000 │
│ [ ] Factura C $3,000 │
│ │
│ Total: $15,000.00 │
│ │
│ [ Pagar Online ] │
└──────────────────────────┘Flujo:
- Cliente selecciona facturas a pagar
- Se muestra total acumulado
- Click "Pagar Online"
- Backend crea pago y retorna URL de redirección
- Cliente es redirigido al gateway (MercadoPago, PagoTIC, etc.)
- Completa el pago en el gateway
- Gateway redirige de vuelta a
/pagar/exito - Se muestra confirmación
APIs:
GET /portal/deudas: Obtener facturasPOST /portal/pagos/iniciar: Iniciar pago
5. /cupones
Propósito: Ver cupones generados y generar nuevos para pago físico.
UI: Lista de cupones con filtros por estado (Todos, Pendientes, Usados, Vencidos).
Datos por cupón:
- Código de barras (19 dígitos)
- Monto
- Estado (Pendiente, Usado, Vencido, Cancelado)
- Fecha de vencimiento
- Recibo vinculado (si fue usado)
Acciones:
- Ver detalle de cupón
- Descargar PDF
- Generar nuevo cupón
APIs:
GET /portal/cupones/cliente/{id}: Listar cuponesPOST /portal/cupones/generar: Generar nuevo cupón
5.1. /cupones/generar
Propósito: Generar PDF con código de barras para pagar en ubicación física.
Flujo:
- Cliente selecciona facturas
- Se genera código de barras único (ITF-14)
- Se crea PDF con:
- Datos del cliente
- Lista de facturas
- Código de barras
- Fecha de vencimiento
- Cliente puede descargar o imprimir
API: POST /portal/cupones/generar
6. /historial-pagos
Propósito: Ver historial completo de pagos realizados.
UI: Lista cronológica de pagos.
Datos por pago:
- Fecha del pago
- Método (Pago Online, Cupón)
- Monto
- Recibo vinculado
- Estado
Acciones:
- Ver detalle del pago
- Ver recibo
API: GET /portal/pagos/historial
Comunicación con Backend
Cliente API (Axios)
Configuración:
- Base URL desde variable de entorno
- Timeout de 10 segundos
- Headers automáticos:
Authorization: Bearer token de sesiónX-Client-Domain: Dominio actual (para multi-tenant)
Interceptores:
- Request: Agregar token y dominio a cada request
- Response: Redirigir a login si 401 (no autorizado)
Módulos API
Cada módulo agrupa endpoints relacionados:
- auth.ts: Identificación de cliente
- ctacte.ts: Cuenta corriente (deudas, saldos)
- pagos.ts: Iniciar pagos, historial
- cupones.ts: Generar, listar, obtener cupones
PWA (Progressive Web App)
Características:
- Instalable: Se puede instalar en el home screen del móvil
- Offline: Service Worker para cache de assets
- Responsive: Diseño mobile-first
- App-like: Se ve y funciona como app nativa
Manifest:
- Nombre de la app (dinámico por tenant)
- Iconos en 192x192 y 512x512
- Color de tema (dinámico por tenant)
- Orientación portrait
Caching Strategy:
- API calls: NetworkFirst (datos frescos, fallback a cache)
- Assets estáticos: CacheFirst (imágenes, CSS, JS)
Branding Dinámico
Propósito: Cada tenant tiene su propia identidad visual.
Configuración por tenant (desde backend):
app_name: Nombre de la aplicaciónshort_name: Nombre corto para PWAlogo_url: URL del logoprimary_color: Color principal (hex)theme_color: Color del tema PWA
Aplicación:
- Al cargar el dominio, se obtiene configuración del tenant
- Se inyectan CSS variables en
document.documentElement:--color-primary--color-secondary
- Se actualiza manifest.json dinámicamente
- Se carga logo desde URL del tenant
Ejemplo:
- Empresa A usa azul (#1e40af) y logo propio
- Empresa B usa verde (#10b981) y logo propio
- Misma aplicación, diferente branding
Custom Hooks
useAuth: Gestionar estado de autenticación useDeudas: Cargar y filtrar deudas usePagos: Iniciar pagos y ver historial useCupones: Listar y generar cupones useBranding: Obtener y aplicar branding del tenant
Ventajas de hooks:
- Reutilización de lógica
- Separación de UI y lógica
- Testing más fácil
Optimizaciones
Code Splitting
Cargar páginas pesadas solo cuando se necesitan (lazy loading):
- PDF generation
- Barcode rendering
- Páginas de historial con muchos datos
Service Worker
Cachear automáticamente:
- HTML/CSS/JS de la aplicación
- API responses (corto tiempo)
- Imágenes y assets estáticos
Beneficios:
- Carga más rápida
- Funciona offline (parcialmente)
- Reduce uso de datos móviles
Flujo de Usuario Completo
- Cliente ingresa al dominio del tenant (ej:
ctacte.empresaa.com.ar) - Sistema resuelve tenant y carga branding
- Cliente se identifica con DNI/CUIT
- Ve dashboard con resumen de cuenta
- Consulta deudas pendientes
- Opción A: Pago Online
- Selecciona facturas
- Paga en gateway
- Ve confirmación
- Opción B: Generar Cupón
- Selecciona facturas
- Genera PDF con código de barras
- Paga en ubicación física
- Ve historial de pagos realizados
Próximos Pasos
- Ver documentación de páginas individuales en pages/
- Revisar componentes reutilizables en components/
- Consultar integración de API en detalle