Appearance
Fase 4 — Configuración de Gateway de Pagos (ERP)
⚠️ DOCUMENTACIÓN RETROSPECTIVA — Generada a partir de código implementado el 2026-04-27.
Módulo: Portal de Clientes — ERP Integration Tipo: View Estado: Implementado Fecha: 2026-04-27
Descripción
Vista del ERP (bautista-app) que permite a un operador configurar, por sucursal activa, el gateway de pagos online que utilizará el Portal de Clientes (PayPerTIC, MercadoPago o "sin pagos online"). La configuración se persiste en la tabla genérica data_config mediante el endpoint config/data, usando claves namespaced bajo el prefijo portal.gateway.*.
La sección se monta dentro de la pantalla "Configuración del Sistema" del módulo de Configuración, junto a la sección de SMTP, y es de uso exclusivo del operador del ERP. El cliente final del portal nunca interactúa con esta vista.
Documento de negocio asociado: gateway-config-view.md.
Ubicación en el ERP
| Aspecto | Detalle |
|---|---|
| Repositorio | bautista-app |
| Módulo del ERP | Configuración del Sistema |
| Vista contenedora | SistemaConfigView |
| Acceso del operador | Inicio → Configuración → Configuración del Sistema |
| Posición dentro de la vista | Segunda sección plegable, debajo de "Configuración SMTP" |
| Estado por defecto | defaultExpanded={false} (plegada) |
La vista se entrega con el patrón de layout estándar del frontend: PageWrapper + PageHeader + PageContent, con cada sección encapsulada en un FormSection (ts/core/components/form/layout/FormSection).
Componentes implementados
GatewayConfigSection
| Aspecto | Detalle |
|---|---|
| Ubicación | bautista-app/ts/mod-config/Sistema/components/GatewayConfigSection.tsx |
| Tipo | Componente funcional React |
| Props | Ninguna — el componente se autoabastece de contexto y queries |
| Retorno | React.JSX.Element | null |
data-testid raíz | gateway-config-section |
Responsabilidades:
- Cargar la configuración actual de gateway desde la API (
GET config/data) vía TanStack Query. - Detectar si la sucursal tiene la configuración base inicializada y, en caso contrario, ocultarse retornando
null. - Renderizar un formulario controlado por React Hook Form con resolver Zod para validar los campos.
- Aplicar render condicional del campo
apiSecretsolo cuando el gateway seleccionado esmercadopago. - Limpiar automáticamente
apiSecretcuando el operador deja de seleccionarmercadopago. - Toggle de visibilidad individual por campo de credencial (apiKey, apiSecret, webhookToken) con íconos
Visibility/VisibilityOffde Material UI. - Mostrar feedback al usuario mediante
showToastdeToastNotificationsen éxito y error. - Mostrar el nombre de la sucursal activa en el encabezado, derivado de
useConfig().usuario.schemay mapeado pormapSucursalToOption.
Comportamiento cuando no hay seed:
- El servicio retorna
nullcuando la claveportal.gateway.nombreno está presente en el diccionario de configuración devuelto porconfig/data. - En ese caso, después del primer
isLoading === false, el componente retornanull(early return en línea 152-154 del componente). - Esto implica que el
FormSectionque lo envuelve queda con un body vacío. La presencia de la sección en el árbol depende deSistemaConfigView, no del componente.
Gateways soportados:
Valor del campo nombre | Etiqueta UI | Requiere apiKey | Requiere apiSecret | Requiere webhookToken |
|---|---|---|---|---|
'' (vacío) | "Sin pagos online" | No | No | No |
paypertic | "PayPerTIC" | Sí | No | Sí |
mercadopago | "MercadoPago" | Sí | Sí | Sí |
Los valores válidos vienen del literal GATEWAY_OPTIONS definido en el schema (['', 'paypertic', 'mercadopago']).
Estados internos (useState):
showApiKey,showApiSecret,showWebhookToken: booleans independientes para alternar visibilidad de cada credencial.
Integraciones externas dentro del componente:
| Hook / utilidad | Origen | Uso |
|---|---|---|
useConfig | core/context/ConfigContext | Lectura del schema de la sucursal activa |
mapSucursalToOption | core/utils | Convierte schema (suc0001) a etiqueta legible |
useForm + Controller | react-hook-form | Form controlado |
zodResolver | @hookform/resolvers/zod | Validación con Zod |
useQuery / useMutation | @tanstack/react-query | Server state |
showToast | core/components/ToastNotifications | Notificaciones |
SistemaConfigView
| Aspecto | Detalle |
|---|---|
| Ubicación | bautista-app/ts/mod-config/Sistema/views/SistemaConfigView.tsx |
| Tipo | Componente funcional React (default export) |
| Props | Ninguna |
Responsabilidades:
- Componer el layout de página (
PageWrapper+PageHeader+PageContent). - Definir los breadcrumbs:
Inicio→Configuración del Sistema. - Apilar dos
FormSection:- "Configuración SMTP" (
defaultExpanded={true}) → renderiza<SmtpConfigSection />. - "Configuración del Gateway de Pagos" (
defaultExpanded={false}) → renderiza<GatewayConfigSection />.
- "Configuración SMTP" (
La integración entre SmtpConfigSection y GatewayConfigSection es por simple coexistencia bajo el mismo PageContent. Cada sección gestiona su propio fetch, su propio formulario y su propia mutación de manera independiente; no comparten state ni cache.
Schema de validación (gatewayConfig.schema.ts)
Ubicación: bautista-app/ts/mod-config/Sistema/schemas/gatewayConfig.schema.ts
Constantes y tipos exportados
| Símbolo | Tipo | Descripción |
|---|---|---|
GATEWAY_OPTIONS | readonly ['', 'paypertic', 'mercadopago'] | Tupla as const con los valores aceptados del campo nombre |
GatewayName | (typeof GATEWAY_OPTIONS)[number] | Unión de literales: '' | 'paypertic' | 'mercadopago' |
gatewayConfigSchema | z.ZodEffects<...> | Schema Zod con refinamiento condicional |
GatewayConfigSchema | z.infer<typeof gatewayConfigSchema> | Tipo inferido para el formulario |
Campos del schema
| Campo | Tipo Zod | Notas |
|---|---|---|
nombre | z.enum(GATEWAY_OPTIONS) | Acepta exactamente los tres valores literales |
apiKey | z.string() | Validado condicionalmente por superRefine |
apiSecret | z.string() | Validado condicionalmente por superRefine |
webhookToken | z.string() | Validado condicionalmente por superRefine |
Validaciones condicionales (superRefine)
La validación principal vive en el bloque superRefine:
| Condición | Campo afectado | Mensaje |
|---|---|---|
nombre === '' | — | Se omiten todas las validaciones de credenciales (early return) |
nombre !== '' y apiKey.length === 0 | apiKey | "El api_key es requerido" |
nombre !== '' y webhookToken.length === 0 | webhookToken | "El webhook_token es requerido" |
nombre === 'mercadopago' y apiSecret.length === 0 | apiSecret | "El api_secret es requerido para MercadoPago" |
Implicancias:
- Cuando
nombre === ''se permite guardar con todas las credenciales vacías (deshabilitar pagos). apiSecretsolo es exigido para MercadoPago. Para PayPerTIC el campo no se renderiza (render condicional en el componente) y el valor remanente se limpia víauseEffect.- No se aplican formatos (length mínima distinta, regex, etc.). Solo presencia.
Service (gatewayConfig.service.ts)
Ubicación: bautista-app/ts/mod-config/Sistema/services/gatewayConfig.service.ts
Constantes y tipos exportados
| Símbolo | Tipo | Descripción |
|---|---|---|
GATEWAY_KEYS | readonly tuple of 4 strings | Lista exhaustiva de claves data_config que maneja el módulo |
GatewayKey | (typeof GATEWAY_KEYS)[number] | Unión de las 4 claves |
GatewayConfigValues | Record<GatewayKey, string> | Diccionario completo de claves → valores string |
GatewayConfigReadResult | GatewayConfigValues | null | Resultado de lectura: null cuando no hay seed |
Claves de configuración manejadas (portal.gateway.*)
Clave data_config | Campo del form | Propósito |
|---|---|---|
portal.gateway.nombre | nombre | Identificador del gateway ('', paypertic, mercadopago) |
portal.gateway.api_key | apiKey | Credencial principal del gateway |
portal.gateway.api_secret | apiSecret | Credencial secreta (solo MercadoPago) |
portal.gateway.webhook_token | webhookToken | Token compartido para validar webhooks entrantes |
El service nunca lee ni escribe claves fuera de portal.gateway.*. No interfiere con la configuración SMTP ni con cualquier otra clave del sistema.
Métodos
GatewayConfigService.getGatewayConfig()
- Firma:
() => Promise<GatewayConfigReadResult> - HTTP:
GET config/data - Forma de la respuesta esperada:
ApiResponse<Record<string, string | null>>. - Retorna
nullcuando la claveportal.gateway.nombreno está presente endata(criterio: presencia de la clave, no su valor — un valor de string vacío sigue indicando seed corrido). - Retorna
GatewayConfigValuescuando la clave existe; cualquier credencial faltante en la respuesta se normaliza a string vacío via?? ''. - Usado dentro del componente como
queryFnconqueryKey: ['gateway-config'].
GatewayConfigService.saveGatewayConfig(values)
- Firma:
(values: GatewayConfigValues) => Promise<void> - HTTP:
PUT config/data - Body:
{ claves: { 'portal.gateway.nombre': ..., 'portal.gateway.api_key': ..., 'portal.gateway.api_secret': ..., 'portal.gateway.webhook_token': ... } } - Solo persiste las 4 claves del namespace
portal.gateway.*. Cualquier otra clave preexistente endata_configqueda intacta porque el endpoint del backend hace upsert por clave. - No retorna datos al consumidor; el componente maneja éxito/error por el ciclo de TanStack Query (
onSuccess/onErrorde la mutación).
Flujo de datos
| Paso | Actor | Acción |
|---|---|---|
| 1 | GatewayConfigSection (mount) | Llama a useQuery({ queryKey: ['gateway-config'], queryFn: GatewayConfigService.getGatewayConfig }) |
| 2 | GatewayConfigService.getGatewayConfig | GET config/data y filtra el dict por presencia de portal.gateway.nombre |
| 3 | Componente (rama A — sin seed) | Si !isLoading && data === null → retorna null y la sección queda vacía |
| 4 | Componente (rama B — con seed) | useEffect con dependencia gatewayConfig ejecuta reset(configToFormValues(gatewayConfig)) para hidratar React Hook Form |
| 5 | Operador | Edita campos. watch('nombre') dispara useEffect que limpia apiSecret si el valor distinto de mercadopago |
| 6 | Operador | Submit (handleSubmit(onSubmit)); el resolver Zod ejecuta gatewayConfigSchema.superRefine |
| 7 | Validación falla | Errores se asignan al formState; disabled={hasErrors} deshabilita el botón "Guardar Gateway" |
| 8 | Validación OK | onSubmit mapea con formToConfigValues y llama saveMutation.mutate(...) |
| 9 | useMutation.mutationFn | Ejecuta GatewayConfigService.saveGatewayConfig(values) → PUT config/data |
| 10 | onSuccess / onError | Muestra toast con showToast.success(...) o showToast.error(...) |
Helpers internos del componente:
| Función | Dirección | Responsabilidad |
|---|---|---|
isGatewayName(value) | guard | Type guard que devuelve true si el string está en GATEWAY_OPTIONS |
configToFormValues(config) | API → form | Normaliza el dict (portal.gateway.*) a la forma del schema; defaultea a '' |
formToConfigValues(data) | form → API | Mapea los campos del form al dict con claves portal.gateway.* |
Tests
Al momento de generar esta documentación retrospectiva, no se encontraron archivos de test (*.test.tsx, *.test.ts) dentro de bautista-app/ts/mod-config/Sistema/ para los artefactos de gateway (GatewayConfigSection, gatewayConfig.schema, gatewayConfig.service).
⚠️ Pendiente de validación: Si los tests viven en otra ubicación (por ejemplo bajo un
tests/global del repo) o aún no fueron escritos, conviene confirmar con el equipo y, en su caso, completar esta sección con el detalle de cobertura.
Las áreas razonablemente testeables identificadas a partir del código son:
| Área | Casos sugeridos |
|---|---|
gatewayConfig.schema | Aceptación de nombre = '' sin credenciales; rechazo de apiKey vacío con nombre = 'paypertic'; rechazo de apiSecret vacío con nombre = 'mercadopago'; aceptación de apiSecret vacío con nombre = 'paypertic' |
gatewayConfig.service.getGatewayConfig | Retorno null cuando la clave portal.gateway.nombre no está presente; mapeo correcto cuando todas las claves vienen; defaulteo a '' cuando vienen como null |
gatewayConfig.service.saveGatewayConfig | Body enviado con exactamente las 4 claves portal.gateway.* |
GatewayConfigSection | Render null cuando service devuelve null; render del campo apiSecret solo con MercadoPago; limpieza de apiSecret al cambiar de gateway; toggle de visibilidad por campo; submit habilita/deshabilita según hasErrors |
Ver también
- Gateway Config — Vista (negocio) — Documento de negocio asociado a esta vista.
- Roadmap de Desarrollo — Fase 4 dentro del plan general del Portal de Clientes.
- Reconciliación de Pagos (Fase 5) — Vista ERP que consume el gateway configurado.
- Payment Gateways (Integraciones) — Adapters backend (PayPerTIC, MercadoPago) que consumen estas credenciales.
bautista-backend— Service PHP que exponeGET/PUT config/datay persiste endata_config.ts/mod-config/Sistema/components/SmtpConfigSection.tsx— Sección hermana enSistemaConfigView, sigue el mismo patrón.
⚠️ NOTA IMPORTANTE: Validar con stakeholders antes de considerar final. Especialmente: confirmar la existencia (o ausencia) y ubicación de tests automatizados, y verificar el contrato exacto del endpoint
config/dataen el backend.