Guía de Desarrollo Frontend CDP
Configuración del Entorno
Requisitos Previos
- Node.js: v18.0.0 o superior
- npm: v9.0.0 o superior
- Git: v2.30.0 o superior
- Editor: VSCode recomendado
Instalación Inicial
# Clonar el repositorio
git clone https://github.com/NomadaDigital01/nerdistan-cdp-frontend.git
cd nerdistan-cdp-frontend
# Instalar dependencias
npm install
# Configurar variables de entorno
cp .env.example .env
# Editar .env con tu configuración
VITE_API_URL=https://nerdistan-datalake-production.up.railway.app
Estructura del Proyecto
nerdistan-cdp-frontend/
├── src/
│ ├── components/ # Componentes React
│ │ ├── DashboardTremor.jsx
│ │ ├── CDPAnalytics.jsx
│ │ ├── DataViewer.jsx
│ │ ├── VTEXIntegrationConfig.jsx
│ │ └── GoogleAds/
│ │ └── AudienceManager.jsx
│ ├── assets/ # Imágenes y recursos
│ ├── styles/ # Estilos globales
│ ├── utils/ # Utilidades y helpers
│ ├── hooks/ # React hooks personalizados
│ ├── App.jsx # Componente principal
│ ├── main.jsx # Entry point
│ └── index.css # Estilos globales
├── public/ # Assets públicos
├── dist/ # Build de producción
├── package.json # Dependencias y scripts
├── vite.config.js # Configuración de Vite
├── tailwind.config.js # Configuración de Tailwind
└── README.md # Documentación
Comandos de Desarrollo
Scripts Disponibles
# Desarrollo local con hot reload
npm run dev
# Build de producción
npm run build
# Preview del build
npm run preview
# Linting (futuro)
npm run lint
# Tests (futuro)
npm run test
Desarrollo Local
# Iniciar servidor de desarrollo
npm run dev
# El servidor estará disponible en:
# http://localhost:5173
Variables de Entorno
# .env.development
VITE_API_URL=http://localhost:8000
VITE_ENV=development
VITE_DEBUG=true
# .env.production
VITE_API_URL=https://nerdistan-datalake-production.up.railway.app
VITE_ENV=production
VITE_DEBUG=false
Flujo de Desarrollo
1. Crear Nueva Feature
# Crear branch desde main
git checkout -b feature/nombre-feature
# Desarrollar la feature
# ... hacer cambios ...
# Commit con mensaje descriptivo
git add .
git commit -m "feat: agregar nueva funcionalidad X"
# Push al repositorio
git push origin feature/nombre-feature
# Crear Pull Request en GitHub
2. Convención de Commits
Seguimos Conventional Commits:
# Features
feat: agregar selector de tenants
# Fixes
fix: corregir cálculo de RFM
# Documentación
docs: actualizar README con nuevos endpoints
# Estilos
style: formatear código con prettier
# Refactor
refactor: simplificar lógica de filtrado
# Tests
test: agregar tests para DataViewer
# Chore
chore: actualizar dependencias
3. Estructura de Componentes
// Plantilla de componente
import React, { useState, useEffect } from 'react';
import { Card, Title, Text } from '@tremor/react';
const ComponentName = ({ prop1, prop2 }) => {
// Estado
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Effects
useEffect(() => {
loadData();
}, [prop1]);
// Handlers
const loadData = async () => {
setLoading(true);
try {
const response = await fetch(`/api/data?param=${prop1}`);
const json = await response.json();
setData(json);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
// Render
if (loading) return <LoadingState />;
if (error) return <ErrorState error={error} />;
return (
<Card>
<Title>{prop2}</Title>
<Text>{data?.description}</Text>
{/* Contenido del componente */}
</Card>
);
};
export default ComponentName;
Guías de Estilo
JavaScript/React
// ✅ Usar const/let, no var
const API_URL = 'https://api.example.com';
let counter = 0;
// ✅ Arrow functions para callbacks
const filtered = data.filter(item => item.active);
// ✅ Destructuring
const { name, email } = user;
const [first, ...rest] = array;
// ✅ Template literals
const message = `Hello ${name}, you have ${count} items`;
// ✅ Async/await sobre promises
const fetchData = async () => {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
console.error(error);
}
};
// ✅ Optional chaining
const city = user?.address?.city || 'Unknown';
// ✅ Nullish coalescing
const value = data ?? defaultValue;
CSS/Tailwind
// ✅ Usar clases de Tailwind
<div className="flex items-center justify-between p-4">
<h1 className="text-2xl font-bold text-gray-900">Title</h1>
</div>
// ✅ Condicionales con clsx
import clsx from 'clsx';
<div className={clsx(
'p-4 rounded',
isActive && 'bg-blue-500',
isError && 'bg-red-500'
)}>
// ❌ Evitar estilos inline
<div style={{ padding: '16px' }}> // No hacer esto
Organización de Imports
// 1. React y hooks
import React, { useState, useEffect } from 'react';
// 2. Librerías externas
import { Card, Title } from '@tremor/react';
import { ArrowPathIcon } from '@heroicons/react/24/outline';
// 3. Componentes locales
import DataTable from '../DataTable';
// 4. Utils y helpers
import { formatCurrency } from '../../utils/format';
// 5. Estilos
import './styles.css';
Debugging
Console Logging
// Solo en desarrollo
if (import.meta.env.DEV) {
console.log('Debug info:', data);
}
// Con grupos
console.group('API Response');
console.log('Status:', response.status);
console.log('Data:', response.data);
console.groupEnd();
// Con timing
console.time('API Call');
await fetchData();
console.timeEnd('API Call');
React DevTools
// Instalar extensión de Chrome/Firefox
// React Developer Tools
// Usar displayName para debugging
ComponentName.displayName = 'ComponentName';
// Props validation (desarrollo)
import PropTypes from 'prop-types';
ComponentName.propTypes = {
tenantId: PropTypes.number.isRequired,
tenantName: PropTypes.string
};
Network Debugging
// Interceptar requests
window.addEventListener('fetch', (event) => {
console.log('Fetching:', event.request.url);
});
// Mock responses en desarrollo
if (import.meta.env.DEV) {
window.fetch = new Proxy(window.fetch, {
apply(target, thisArg, args) {
console.log('Fetch:', args[0]);
return target.apply(thisArg, args);
}
});
}
Performance
Optimizaciones Comunes
// 1. Memoización de componentes
import { memo } from 'react';
const ExpensiveComponent = memo(({ data }) => {
return <ComplexVisualization data={data} />;
});
// 2. useMemo para cálculos costosos
import { useMemo } from 'react';
const processedData = useMemo(() => {
return heavyProcessing(rawData);
}, [rawData]);
// 3. useCallback para funciones estables
import { useCallback } from 'react';
const handleClick = useCallback((id) => {
doSomething(id);
}, [dependency]);
// 4. Lazy loading de componentes
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
// 5. Virtual scrolling para listas largas
import { FixedSizeList } from 'react-window';
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
>
{Row}
</FixedSizeList>
Monitoreo de Performance
// Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);
// React Profiler
import { Profiler } from 'react';
<Profiler id="Dashboard" onRender={onRenderCallback}>
<Dashboard />
</Profiler>
const onRenderCallback = (id, phase, actualDuration) => {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
};
Testing (Futuro)
Unit Tests con Vitest
// component.test.jsx
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import Component from './Component';
describe('Component', () => {
it('renders correctly', () => {
render(<Component title="Test" />);
expect(screen.getByText('Test')).toBeInTheDocument();
});
it('handles click events', async () => {
const handleClick = vi.fn();
render(<Component onClick={handleClick} />);
await userEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledOnce();
});
});
Integration Tests
// integration.test.jsx
import { describe, it, expect } from 'vitest';
import { render, waitFor } from '@testing-library/react';
import Dashboard from './Dashboard';
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('Dashboard Integration', () => {
it('loads and displays tenant data', async () => {
render(<Dashboard />);
await waitFor(() => {
expect(screen.getByText('65,226 customers')).toBeInTheDocument();
});
});
});
Deployment
Build de Producción
# Build optimizado
npm run build
# Verificar el build
npm run preview
# Analizar bundle size
npx vite-bundle-visualizer
Configuración de Railway
# railway.json
{
"build": {
"builder": "NIXPACKS",
"buildCommand": "npm run build",
"watchPatterns": ["src/**"]
},
"deploy": {
"startCommand": "npm run preview",
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10
}
}
GitHub Actions CI/CD
# .github/workflows/deploy.yml
name: Deploy to Railway
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
- name: Deploy to Railway
uses: bervProject/railway-deploy@main
with:
railway_token: ${{ secrets.RAILWAY_TOKEN }}
Troubleshooting
Problemas Comunes
1. CORS Errors
// Solución: Verificar configuración de backend
// O usar proxy en desarrollo
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true
}
}
}
});
2. Build Failures
# Limpiar cache
rm -rf node_modules dist
npm install
npm run build
3. Memory Issues
# Aumentar memoria para Node
NODE_OPTIONS="--max-old-space-size=4096" npm run build
4. Hot Reload No Funciona
// vite.config.js
export default defineConfig({
server: {
watch: {
usePolling: true // Para Docker/WSL
}
}
});
Recursos
Documentación Oficial
Herramientas Útiles
Comunidad
Checklist de Desarrollo
Antes de Commitear
- Código formateado con Prettier
- Sin errores en consola
- Sin warnings de React
- Responsive en móvil y desktop
- Datos reales, no dummy
- Error handling implementado
- Loading states agregados
Antes de Pull Request
- Branch actualizado con main
- Commits siguiendo convención
- Documentación actualizada
- Screenshots si hay cambios UI
- Testeado en producción-like
- Sin console.logs de debug
- Performance verificada
Review Checklist
- Código legible y mantenible
- Sigue patrones del proyecto
- No introduce deuda técnica
- Seguridad considerada
- Accesibilidad verificada
- Tests agregados (futuro)
- Documentación clara