Componentes del Frontend CDP
Visión General
El frontend del CDP está construido con una arquitectura de componentes modulares y reutilizables. Cada componente está diseñado para ser independiente, testeable y fácil de mantener.
Componentes Principales
DashboardTremor
Ubicación: src/components/DashboardTremor.jsx
Propósito: Componente principal que orquesta todo el dashboard
Props
No recibe props - es el componente raíz
Estado
const [selectedTenant, setSelectedTenant] = useState(56); // Tenant por defecto
const [tenantName, setTenantName] = useState('Chelsea IO - Exit');
Características
- Selector de Tenants: Dropdown con 5 tenants principales (202K+ consumidores total)
- Sistema de Tabs: 5 pestañas principales (Overview, CDP Analytics, Data Viewer, Google Ads, VTEX)
- Responsive Design: Adaptable a móviles y desktop
- Auto-refresh: Actualización automática cada 30 segundos
Tenants Disponibles
{
56: 'Chelsea IO - Exit (65.2K consumidores)',
52: 'Celada SA - BAPRO (45.3K consumidores)',
53: 'Cooperativa de Trabajo (15.9K consumidores)',
55: 'EL DORADO SOCIEDAD (11.7K consumidores)',
1: 'PZ Interamericana Textiles (11.4K consumidores)'
}
Ejemplo de Uso
// En App.jsx
import DashboardTremor from './components/DashboardTremor';
function App() {
return <DashboardTremor />;
}
CDPAnalytics
Ubicación: src/components/CDPAnalytics.jsx
Propósito: Análisis completo del Customer Data Platform
Props
interface CDPAnalyticsProps {
tenantId: number; // ID del tenant actual
tenantName: string; // Nombre del tenant para display
}
Funcionalidades
1. Análisis RFM
- Endpoint:
/api/v2/cdp/analytics/rfm - Segmentos: 8 segmentos RFM (Champions, Loyal, En Riesgo, etc.)
- Visualización: Cards con métricas y distribución por segmento
- Datos: Recency promedio, Frequency, Monetary value
2. Predicción de Churn
- Endpoint:
/api/v2/cdp/analytics/churn - ML Model: Predicción de probabilidad de abandono
- Risk Levels: Alto (>70%), Medio (40-70%), Bajo (<40%)
- Acciones: Recomendaciones automáticas por nivel de riesgo
3. Perfiles de Cliente
- Endpoint:
/api/v2/cdp/analytics/customer-profile - Detalles: Historial de compras, CLV, segmentación
- Búsqueda: Por email, ID o nombre
- Timeline: Visualización cronológica de interacciones
Estado Interno
const [loading, setLoading] = useState(false);
const [activeTab, setActiveTab] = useState('rfm');
const [rfmData, setRfmData] = useState(null);
const [churnData, setChurnData] = useState(null);
const [error, setError] = useState(null);
Ejemplo de Integración
<CDPAnalytics
tenantId={56}
tenantName="Chelsea IO - Exit"
/>
DataViewer
Ubicación: src/components/DataViewer.jsx
Propósito: Visualización de datos raw del CDP con export CSV
Props
interface DataViewerProps {
tenantId: number;
tenantName: string;
}
Características
- Tabla de Clientes: Lista paginada de clientes con todos sus atributos
- Métricas Agregadas: Total clientes, ingresos, ticket promedio
- Export CSV: Descarga de datos en formato CSV
- Filtros: Por segmento, rango de fechas, valor monetario
- Simulación de Datos: Genera datos de ejemplo cuando no hay reales
Columnas de la Tabla
- ID Cliente
- Nombre
- Segmento RFM
- Frecuencia de compra
- Valor promedio
- CLV predicho
- Riesgo de churn (%)
Funciones Principales
const loadData = async () => {
// Carga datos desde /api/v2/cdp/analytics/rfm
};
const exportToCSV = () => {
// Exporta tabla actual a CSV
};
const getSegmentColor = (segment) => {
// Retorna color Tremor según segmento
};
VTEXIntegrationConfig
Ubicación: src/components/VTEXIntegrationConfig.jsx
Propósito: Configuración de credenciales VTEX por tenant
Props
interface VTEXIntegrationConfigProps {
tenantId: number;
tenantName: string;
}
Campos de Configuración
- Account Name: Nombre de la cuenta VTEX
- App Key: Clave de aplicación VTEX
- App Token: Token de aplicación VTEX
- Environment: vtexcommercestable (producción) o vtexcommercebeta
Endpoints
GET /api/v2/tenant-integrations/{tenant_id}/vtex- Obtener configPOST /api/v2/tenant-integrations/{tenant_id}/vtex- Crear configPUT /api/v2/tenant-integrations/{tenant_id}/vtex- ActualizarDELETE /api/v2/tenant-integrations/{tenant_id}/vtex- EliminarPOST /api/v2/tenant-integrations/{tenant_id}/vtex/test- Test conexión
Validaciones
// Validación de campos requeridos
const isValid = account && appKey && appToken && environment;
// Test de conexión antes de guardar
const testConnection = async () => {
const response = await fetch(
`${API_URL}/api/v2/tenant-integrations/${tenantId}/vtex/test`,
{ method: 'POST', body: JSON.stringify(config) }
);
};
GoogleAds/AudienceManager
Ubicación: src/components/GoogleAds/AudienceManager.jsx
Propósito: Gestión de audiencias para Google Ads Customer Match
Props
interface AudienceManagerProps {
tenantId: number;
tenantName: string;
}
Funcionalidades
- Lista de Tenants: Vista de todos los tenants con estadísticas
- Upload de Audiencias: Carga masiva a Google Ads
- Historial: Registro de uploads anteriores
- Métricas: Clientes listos vs. en proceso
- Auto-refresh: Actualización cada 30 segundos
Estado de Tenants
const tenantStats = {
totalCustomers: 65226,
readyForUpload: 45000,
inProgress: 20226,
lastUpload: '2024-01-15',
status: 'ready' // ready | processing | error
};
Flujo de Upload
- Seleccionar tenant
- Elegir cuenta de Google Ads
- Configurar opciones (hash emails, incluir teléfonos)
- Iniciar upload
- Monitorear progreso
- Verificar en Google Ads
Componentes de UI (Tremor)
Card
import { Card, Title, Text } from '@tremor/react';
<Card>
<Title>Título</Title>
<Text>Descripción</Text>
{children}
</Card>
Metric
import { Metric } from '@tremor/react';
<Metric>{formatNumber(123456)}</Metric>
// Output: 123,456
TabGroup/TabList
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@tremor/react';
<TabGroup>
<TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
</TabList>
<TabPanels>
<TabPanel>Content 1</TabPanel>
<TabPanel>Content 2</TabPanel>
</TabPanels>
</TabGroup>
Table
import {
Table, TableHead, TableRow, TableHeaderCell,
TableBody, TableCell
} from '@tremor/react';
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Header</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Cell</TableCell>
</TableRow>
</TableBody>
</Table>
Badge
import { Badge } from '@tremor/react';
<Badge color="emerald">Success</Badge>
<Badge color="rose">Error</Badge>
<Badge color="amber">Warning</Badge>
Button
import { Button } from '@tremor/react';
import { ArrowPathIcon } from '@heroicons/react/24/outline';
<Button
icon={ArrowPathIcon}
onClick={handleClick}
loading={isLoading}
variant="primary" // primary | secondary | light
size="sm" // xs | sm | md | lg | xl
>
Actualizar
</Button>
Select
import { Select, SelectItem } from '@tremor/react';
<Select value={value} onValueChange={setValue}>
<SelectItem value="1">Option 1</SelectItem>
<SelectItem value="2">Option 2</SelectItem>
</Select>
ProgressBar
import { ProgressBar } from '@tremor/react';
<ProgressBar value={75} color="blue" />
Callout
import { Callout } from '@tremor/react';
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
<Callout
title="Advertencia"
icon={ExclamationTriangleIcon}
color="amber"
>
Mensaje de advertencia
</Callout>
Patrones de Componentes
1. Loading States
const Component = () => {
const [loading, setLoading] = useState(false);
if (loading) {
return (
<Card>
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
</div>
</Card>
);
}
return <ActualContent />;
};
2. Error Handling
const Component = () => {
const [error, setError] = useState(null);
if (error) {
return (
<Callout
title="Error"
color="rose"
icon={ExclamationTriangleIcon}
>
{error}
</Callout>
);
}
return <ActualContent />;
};
3. Empty States
const Component = ({ data }) => {
if (!data || data.length === 0) {
return (
<Card>
<div className="text-center py-8">
<CircleStackIcon className="h-12 w-12 text-gray-400 mx-auto" />
<Text className="mt-2">No hay datos disponibles</Text>
<Button onClick={reload} className="mt-4">
Recargar
</Button>
</div>
</Card>
);
}
return <DataTable data={data} />;
};
4. Responsive Grid
import { Grid, Col } from '@tremor/react';
<Grid numItems={1} numItemsSm={2} numItemsLg={3} className="gap-4">
<Col>
<Card>Content 1</Card>
</Col>
<Col>
<Card>Content 2</Card>
</Col>
<Col>
<Card>Content 3</Card>
</Col>
</Grid>
Composición de Componentes
Ejemplo Completo: Dashboard con Datos
const Dashboard = () => {
const [tenant, setTenant] = useState(56);
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
loadData();
}, [tenant]);
const loadData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/data?tenant=${tenant}`);
if (!response.ok) throw new Error('Failed to load');
const json = await response.json();
setData(json);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<div className="p-6">
<TenantSelector value={tenant} onChange={setTenant} />
{error && <ErrorCallout message={error} />}
{loading ? (
<LoadingState />
) : data ? (
<DataGrid data={data} />
) : (
<EmptyState onReload={loadData} />
)}
</div>
);
};
Optimizaciones de Performance
1. Memoización
import { useMemo } from 'react';
const ExpensiveComponent = ({ data }) => {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
calculated: expensiveCalculation(item)
}));
}, [data]);
return <DataTable data={processedData} />;
};
2. Lazy Loading
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
<Suspense fallback={<LoadingSpinner />}>
<HeavyComponent />
</Suspense>
3. Debouncing
import { useCallback } from 'react';
const SearchComponent = () => {
const debouncedSearch = useCallback(
debounce((query) => {
performSearch(query);
}, 300),
[]
);
return (
<input
onChange={(e) => debouncedSearch(e.target.value)}
/>
);
};
Testing (Futuro)
Unit Tests
// Component.test.jsx
import { render, screen } from '@testing-library/react';
import Component from './Component';
test('renders component', () => {
render(<Component />);
expect(screen.getByText('Expected Text')).toBeInTheDocument();
});
Integration Tests
// Integration.test.jsx
import { render, waitFor } from '@testing-library/react';
import Dashboard from './Dashboard';
test('loads and displays data', async () => {
render(<Dashboard />);
await waitFor(() => {
expect(screen.getByText('Data Loaded')).toBeInTheDocument();
});
});
Guía de Estilo
Nomenclatura
- Componentes: PascalCase (
DashboardTremor.jsx) - Funciones: camelCase (
loadData()) - Constantes: UPPER_SNAKE_CASE (
API_URL) - Props: camelCase (
tenantId)
Estructura de Archivos
Component.jsx
├── Imports
├── Constants
├── Component Definition
│ ├── State
│ ├── Effects
│ ├── Handlers
│ └── Render
└── Export
Comentarios
// Comentarios single-line para explicaciones breves
/**
* Comentarios multi-line para documentación
* @param {number} tenantId - ID del tenant
* @returns {Promise<Object>} Datos del tenant
*/