feat: Implement Phase 1 & 2 - Full monorepo architecture

## Backend API (apps/api)
- Express.js server with TypeScript
- JWT authentication with access/refresh tokens
- Multi-tenant middleware (schema per tenant)
- Complete CRUD routes: auth, cfdis, transactions, contacts, categories, metrics, alerts
- SAT integration: CFDI 4.0 XML parser, FIEL authentication
- Metrics engine: 50+ financial metrics (Core, Startup, Enterprise)
- Rate limiting, CORS, Helmet security

## Frontend Web (apps/web)
- Next.js 14 with App Router
- Authentication pages: login, register, forgot-password
- Dashboard layout with Sidebar and Header
- Dashboard pages: overview, cash-flow, revenue, expenses, metrics
- Zustand stores for auth and UI state
- Theme support with flash prevention

## Database Package (packages/database)
- PostgreSQL migrations with multi-tenant architecture
- Public schema: plans, tenants, users, sessions, subscriptions
- Tenant schema: sat_credentials, cfdis, transactions, contacts, accounts, alerts
- Tenant management functions
- Seed data for plans and super admin

## Shared Package (packages/shared)
- TypeScript types: auth, tenant, financial, metrics, reports
- Zod validation schemas for all entities
- Utility functions for formatting

## UI Package (packages/ui)
- Chart components: LineChart, BarChart, AreaChart, PieChart
- Data components: DataTable, MetricCard, KPICard, AlertBadge
- PeriodSelector and Skeleton components

## Infrastructure
- Docker Compose: PostgreSQL 15, Redis 7, MinIO, Mailhog
- Makefile with 25+ development commands
- Development scripts: dev-setup.sh, dev-down.sh
- Complete .env.example template

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 11:05:24 +00:00
parent c1321c3f0c
commit a9b1994c48
110 changed files with 40788 additions and 0 deletions

212
apps/web/src/lib/api.ts Normal file
View File

@@ -0,0 +1,212 @@
/**
* Cliente API para comunicación con el backend
*/
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api';
interface ApiResponse<T = unknown> {
data?: T;
error?: string;
message?: string;
status: number;
}
interface RequestOptions extends Omit<RequestInit, 'body'> {
body?: unknown;
params?: Record<string, string | number | boolean | undefined>;
}
class ApiError extends Error {
status: number;
data?: unknown;
constructor(message: string, status: number, data?: unknown) {
super(message);
this.name = 'ApiError';
this.status = status;
this.data = data;
}
}
/**
* Obtiene el token de autenticación del storage
*/
function getAuthToken(): string | null {
if (typeof window === 'undefined') return null;
try {
const stored = localStorage.getItem('horux-auth-storage');
if (stored) {
const parsed = JSON.parse(stored);
return parsed?.state?.token || null;
}
} catch {
return null;
}
return null;
}
/**
* Construye la URL con query params
*/
function buildUrl(endpoint: string, params?: Record<string, string | number | boolean | undefined>): string {
const url = new URL(endpoint.startsWith('http') ? endpoint : `${API_BASE_URL}${endpoint}`);
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, String(value));
}
});
}
return url.toString();
}
/**
* Cliente fetch base con manejo de errores y auth
*/
async function fetchApi<T>(endpoint: string, options: RequestOptions = {}): Promise<ApiResponse<T>> {
const { body, params, headers: customHeaders, ...restOptions } = options;
const token = getAuthToken();
const headers: HeadersInit = {
'Content-Type': 'application/json',
...customHeaders,
};
if (token) {
(headers as Record<string, string>)['Authorization'] = `Bearer ${token}`;
}
const config: RequestInit = {
...restOptions,
headers,
};
if (body) {
config.body = JSON.stringify(body);
}
try {
const url = buildUrl(endpoint, params);
const response = await fetch(url, config);
let data: T | undefined;
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
data = await response.json();
}
if (!response.ok) {
const errorMessage = (data as { message?: string })?.message ||
(data as { error?: string })?.error ||
`Error ${response.status}`;
throw new ApiError(errorMessage, response.status, data);
}
return {
data,
status: response.status,
};
} catch (error) {
if (error instanceof ApiError) {
throw error;
}
throw new ApiError(
error instanceof Error ? error.message : 'Error de conexión',
0
);
}
}
/**
* API Client con métodos HTTP
*/
export const api = {
get<T>(endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>> {
return fetchApi<T>(endpoint, { ...options, method: 'GET' });
},
post<T>(endpoint: string, body?: unknown, options?: RequestOptions): Promise<ApiResponse<T>> {
return fetchApi<T>(endpoint, { ...options, method: 'POST', body });
},
put<T>(endpoint: string, body?: unknown, options?: RequestOptions): Promise<ApiResponse<T>> {
return fetchApi<T>(endpoint, { ...options, method: 'PUT', body });
},
patch<T>(endpoint: string, body?: unknown, options?: RequestOptions): Promise<ApiResponse<T>> {
return fetchApi<T>(endpoint, { ...options, method: 'PATCH', body });
},
delete<T>(endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>> {
return fetchApi<T>(endpoint, { ...options, method: 'DELETE' });
},
};
/**
* Endpoints tipados para la API
*/
export const endpoints = {
// Auth
auth: {
login: '/auth/login',
register: '/auth/register',
logout: '/auth/logout',
me: '/auth/me',
refresh: '/auth/refresh',
},
// Users
users: {
base: '/users',
byId: (id: string) => `/users/${id}`,
profile: '/users/profile',
},
// Strategies
strategies: {
base: '/strategies',
byId: (id: string) => `/strategies/${id}`,
activate: (id: string) => `/strategies/${id}/activate`,
deactivate: (id: string) => `/strategies/${id}/deactivate`,
backtest: (id: string) => `/strategies/${id}/backtest`,
},
// Trades
trades: {
base: '/trades',
byId: (id: string) => `/trades/${id}`,
active: '/trades/active',
history: '/trades/history',
},
// Portfolio
portfolio: {
base: '/portfolio',
balance: '/portfolio/balance',
positions: '/portfolio/positions',
performance: '/portfolio/performance',
},
// Market Data
market: {
prices: '/market/prices',
ticker: (symbol: string) => `/market/ticker/${symbol}`,
candles: (symbol: string) => `/market/candles/${symbol}`,
},
// Analytics
analytics: {
dashboard: '/analytics/dashboard',
performance: '/analytics/performance',
risk: '/analytics/risk',
},
} as const;
export { ApiError };
export type { ApiResponse, RequestOptions };