# US-FUND-004: Infraestructura Técnica Base

**Epic:** MAI-001 - Fundamentos del Sistema
**Story Points:** 12
**Prioridad:** Alta
**Dependencias:**
- Ninguna (es la base del proyecto)

**Estado:** Pendiente
**Asignado a:** DevOps + Backend Lead + Frontend Lead

---

## 📋 Historia de Usuario

**Como** equipo de desarrollo
**Quiero** tener configurada toda la infraestructura técnica base del proyecto
**Para** poder comenzar a desarrollar las funcionalidades del sistema de construcción sobre una base sólida, escalable y mantenible.

---

## 🎯 Contexto y Objetivos

### Contexto

Esta historia cubre la configuración inicial completa del proyecto antes de comenzar el desarrollo de funcionalidades. Incluye:

- **Base de datos PostgreSQL** con schemas organizados
- **Backend NestJS** con estructura modular
- **Frontend React + Vite** con TypeScript
- **Herramientas de desarrollo** (linting, formatting, testing)
- **CI/CD pipeline** básico
- **Docker containers** para desarrollo local

### Objetivos

1. ✅ Proyecto ejecutable localmente en < 5 minutos (para nuevos devs)
2. ✅ Estructura modular lista para escalar
3. ✅ Database migrations automáticas
4. ✅ Hot reload en desarrollo (backend y frontend)
5. ✅ Code quality garantizada (pre-commit hooks)
6. ✅ Tests automatizados en CI/CD

---

## ✅ Criterios de Aceptación

### CA-1: Database Setup

**Dado** un PostgreSQL 15+ instalado localmente o en Docker
**Cuando** ejecuto el script de setup inicial
**Entonces**:

- ✅ Se crea la base de datos `erp_construccion`
- ✅ Se crean los schemas: `auth_management`, `projects`, `budgets`, `purchases`, `hr`, `gamification_system`
- ✅ Se ejecutan todas las migraciones iniciales
- ✅ Se crean las funciones de utilidad (get_current_constructora_id, etc.)
- ✅ Se habilita RLS en todas las tablas de negocio

### CA-2: Backend Structure

**Dado** el proyecto de backend
**Cuando** examino la estructura de carpetas
**Entonces**:

- ✅ Existe una estructura modular clara:
  ```
  apps/backend/src/
  ├── modules/
  │   ├── auth/
  │   ├── users/
  │   ├── constructoras/
  │   ├── projects/
  │   └── ... (módulos por dominio)
  ├── common/
  │   ├── guards/
  │   ├── decorators/
  │   ├── interceptors/
  │   └── filters/
  ├── config/
  │   ├── database.config.ts
  │   ├── jwt.config.ts
  │   └── ...
  └── main.ts
  ```
- ✅ Cada módulo sigue el patrón: `module`, `controller`, `service`, `entity`, `dto`
- ✅ TypeORM configurado con migrations automáticas
- ✅ Swagger UI disponible en `/api/docs`

### CA-3: Frontend Structure

**Dado** el proyecto de frontend
**Cuando** examino la estructura de carpetas
**Entonces**:

- ✅ Existe una estructura clara:
  ```
  apps/frontend/src/
  ├── features/
  │   ├── auth/
  │   ├── dashboard/
  │   ├── projects/
  │   └── ... (features por módulo)
  ├── components/
  │   ├── ui/        (componentes reutilizables)
  │   └── layout/
  ├── stores/        (Zustand stores)
  ├── services/      (API clients)
  ├── hooks/
  ├── utils/
  └── App.tsx
  ```
- ✅ Vite configurado con hot reload
- ✅ TypeScript strict mode habilitado
- ✅ Path aliases configurados (`@/components`, `@/features`, etc.)

### CA-4: Development Tools

**Dado** el proyecto completo
**Cuando** un nuevo desarrollador clona el repo
**Entonces**:

- ✅ `npm install` instala todas las dependencias
- ✅ `npm run dev` levanta backend + frontend + database
- ✅ ESLint + Prettier configurados y funcionando
- ✅ Husky pre-commit hooks ejecutan lint + format
- ✅ Tests pueden ejecutarse con `npm test`

### CA-5: Docker Setup

**Dado** Docker instalado en el sistema
**Cuando** ejecuto `docker-compose up`
**Entonces**:

- ✅ Se levanta PostgreSQL en puerto 5432
- ✅ Se levanta backend en puerto 3000
- ✅ Se levanta frontend en puerto 5173
- ✅ Hot reload funciona dentro de los containers
- ✅ Migrations se ejecutan automáticamente al iniciar backend

### CA-6: CI/CD Pipeline

**Dado** un commit pusheado a GitHub
**Cuando** se activa el pipeline de CI
**Entonces**:

- ✅ Se ejecutan linters (ESLint)
- ✅ Se ejecutan formatters (Prettier)
- ✅ Se ejecutan tests unitarios
- ✅ Se ejecutan tests de integración
- ✅ Se genera reporte de cobertura
- ✅ El pipeline falla si cualquier check no pasa

---

## 🔧 Especificación Técnica Detallada

### 1. Database Setup

#### Script de Inicialización

**Archivo:** `apps/database/scripts/init-database.sh`

```bash
#!/bin/bash

# Configuración
DB_NAME="gamilit_construction"
DB_USER="gamilit_user"
DB_PASSWORD="secure_password_here"
DB_HOST="localhost"
DB_PORT="5432"

# Crear base de datos
psql -U postgres -h $DB_HOST -p $DB_PORT <<EOF
CREATE DATABASE $DB_NAME;
CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
ALTER USER $DB_USER WITH SUPERUSER; -- Para crear extensiones
EOF

# Conectar a la BD y crear schemas
psql -U $DB_USER -d $DB_NAME -h $DB_HOST -p $DB_PORT <<EOF
-- Crear extensiones
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- Crear schemas
CREATE SCHEMA IF NOT EXISTS auth_management;
CREATE SCHEMA IF NOT EXISTS projects;
CREATE SCHEMA IF NOT EXISTS budgets;
CREATE SCHEMA IF NOT EXISTS purchases;
CREATE SCHEMA IF NOT EXISTS finance;
CREATE SCHEMA IF NOT EXISTS hr;
CREATE SCHEMA IF NOT EXISTS post_sales;
CREATE SCHEMA IF NOT EXISTS gamification_system;
CREATE SCHEMA IF NOT EXISTS audit;

-- Otorgar permisos
GRANT ALL ON SCHEMA auth_management TO $DB_USER;
GRANT ALL ON SCHEMA projects TO $DB_USER;
GRANT ALL ON SCHEMA budgets TO $DB_USER;
GRANT ALL ON SCHEMA purchases TO $DB_USER;
GRANT ALL ON SCHEMA finance TO $DB_USER;
GRANT ALL ON SCHEMA hr TO $DB_USER;
GRANT ALL ON SCHEMA post_sales TO $DB_USER;
GRANT ALL ON SCHEMA gamification_system TO $DB_USER;
GRANT ALL ON SCHEMA audit TO $DB_USER;

-- Crear función de contexto (para RLS)
CREATE OR REPLACE FUNCTION auth_management.get_current_constructora_id()
RETURNS UUID AS $$
BEGIN
  RETURN NULLIF(current_setting('app.current_constructora_id', true), '')::UUID;
EXCEPTION
  WHEN OTHERS THEN
    RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;

CREATE OR REPLACE FUNCTION auth_management.get_current_user_id()
RETURNS UUID AS $$
BEGIN
  RETURN NULLIF(current_setting('app.current_user_id', true), '')::UUID;
EXCEPTION
  WHEN OTHERS THEN
    RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;

CREATE OR REPLACE FUNCTION auth_management.get_current_user_role()
RETURNS TEXT AS $$
BEGIN
  RETURN current_setting('app.current_user_role', true);
EXCEPTION
  WHEN OTHERS THEN
    RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;

-- Crear ENUM para roles
CREATE TYPE auth_management.construction_role AS ENUM (
  'director',
  'engineer',
  'resident',
  'purchases',
  'finance',
  'hr',
  'post_sales'
);

-- Crear ENUM para estados de cuenta
CREATE TYPE auth_management.account_status AS ENUM (
  'pending',
  'active',
  'inactive',
  'suspended',
  'banned'
);

COMMENT ON FUNCTION auth_management.get_current_constructora_id() IS 'Obtiene el ID de la constructora del contexto de la sesión';
COMMENT ON FUNCTION auth_management.get_current_user_id() IS 'Obtiene el ID del usuario del contexto de la sesión';
COMMENT ON FUNCTION auth_management.get_current_user_role() IS 'Obtiene el rol del usuario del contexto de la sesión';
EOF

echo "✅ Base de datos inicializada correctamente"
```

#### Migración Inicial - Tabla de Constructoras

**Archivo:** `apps/backend/src/migrations/1700000000001-CreateConstructoras.ts`

```typescript
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateConstructoras1700000000001 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE auth_management.constructoras (
        id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
        name VARCHAR(255) NOT NULL,
        rfc VARCHAR(13) UNIQUE NOT NULL,
        business_name VARCHAR(255) NOT NULL,
        phone VARCHAR(20),
        email VARCHAR(255),
        address TEXT,
        city VARCHAR(100),
        state VARCHAR(100),
        country VARCHAR(100) DEFAULT 'México',
        postal_code VARCHAR(10),
        logo_url TEXT,
        settings JSONB DEFAULT '{}'::jsonb,
        is_active BOOLEAN DEFAULT true,
        created_at TIMESTAMPTZ DEFAULT NOW(),
        updated_at TIMESTAMPTZ DEFAULT NOW(),
        deleted_at TIMESTAMPTZ
      );

      -- Índices
      CREATE INDEX idx_constructoras_rfc ON auth_management.constructoras(rfc);
      CREATE INDEX idx_constructoras_is_active ON auth_management.constructoras(is_active) WHERE deleted_at IS NULL;

      -- Trigger de actualización
      CREATE TRIGGER set_constructoras_updated_at
        BEFORE UPDATE ON auth_management.constructoras
        FOR EACH ROW
        EXECUTE FUNCTION auth_management.update_updated_at_column();
    `);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE auth_management.constructoras CASCADE;`);
  }
}
```

---

### 2. Backend NestJS - Estructura

#### Main Application Bootstrap

**Archivo:** `apps/backend/src/main.ts`

```typescript
import { NestFactory } from '@nestjs/core';
import { ValidationPipe, VersioningType } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ConfigService } from '@nestjs/config';
import helmet from 'helmet';
import * as compression from 'compression';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
import { LoggingInterceptor } from './common/interceptors/logging.interceptor';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: ['error', 'warn', 'log', 'debug', 'verbose'],
  });

  const configService = app.get(ConfigService);

  // Security
  app.use(helmet());
  app.enableCors({
    origin: configService.get('CORS_ORIGINS')?.split(',') || ['http://localhost:5173'],
    credentials: true,
  });

  // Compression
  app.use(compression());

  // Global prefix
  app.setGlobalPrefix('api');

  // API Versioning
  app.enableVersioning({
    type: VersioningType.URI,
    defaultVersion: '1',
  });

  // Global pipes
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  );

  // Global filters
  app.useGlobalFilters(new HttpExceptionFilter());

  // Global interceptors
  app.useGlobalInterceptors(
    new LoggingInterceptor(),
    new TransformInterceptor(),
  );

  // Swagger documentation
  if (configService.get('NODE_ENV') !== 'production') {
    const config = new DocumentBuilder()
      .setTitle('Sistema de Gestión de Obra - API')
      .setDescription('API RESTful para gestión integral de proyectos de construcción')
      .setVersion('1.0')
      .addBearerAuth(
        {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
          name: 'JWT',
          description: 'Enter JWT token',
          in: 'header',
        },
        'JWT-auth',
      )
      .addTag('Auth', 'Autenticación y autorización')
      .addTag('Users', 'Gestión de usuarios')
      .addTag('Constructoras', 'Gestión de constructoras (multi-tenancy)')
      .addTag('Projects', 'Gestión de proyectos')
      .addTag('Budgets', 'Gestión de presupuestos')
      .addTag('Purchases', 'Gestión de compras y proveedores')
      .addTag('HR', 'Recursos humanos y asistencias')
      .addTag('Finance', 'Finanzas y tesorería')
      .addTag('Post-Sales', 'Post-venta y garantías')
      .build();

    const document = SwaggerModule.createDocument(app, config);
    SwaggerModule.setup('api/docs', app, document);
  }

  const port = configService.get('PORT') || 3000;
  await app.listen(port);

  console.log(`🚀 Application is running on: http://localhost:${port}/api`);
  console.log(`📚 Swagger docs available at: http://localhost:${port}/api/docs`);
}

bootstrap();
```

#### Database Configuration

**Archivo:** `apps/backend/src/config/database.config.ts`

```typescript
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';

export const getDatabaseConfig = (
  configService: ConfigService,
): TypeOrmModuleOptions => ({
  type: 'postgres',
  host: configService.get('DB_HOST', 'localhost'),
  port: configService.get('DB_PORT', 5432),
  username: configService.get('DB_USERNAME', 'gamilit_user'),
  password: configService.get('DB_PASSWORD'),
  database: configService.get('DB_DATABASE', 'gamilit_construction'),
  entities: [__dirname + '/../**/*.entity{.ts,.js}'],
  migrations: [__dirname + '/../migrations/*{.ts,.js}'],
  synchronize: false, // SIEMPRE false en producción
  logging: configService.get('NODE_ENV') === 'development' ? ['query', 'error'] : ['error'],
  migrationsRun: true, // Auto-run migrations on startup
  ssl: configService.get('DB_SSL') === 'true' ? { rejectUnauthorized: false } : false,
  extra: {
    max: 20, // Max pool size
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 2000,
  },
});
```

#### App Module

**Archivo:** `apps/backend/src/app.module.ts`

```typescript
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ThrottlerModule } from '@nestjs/throttler';
import { ScheduleModule } from '@nestjs/schedule';
import { EventEmitterModule } from '@nestjs/event-emitter';

import { getDatabaseConfig } from './config/database.config';

// Modules
import { AuthModule } from './modules/auth/auth.module';
import { UsersModule } from './modules/users/users.module';
import { ConstructorasModule } from './modules/constructoras/constructoras.module';
import { ProjectsModule } from './modules/projects/projects.module';
import { BudgetsModule } from './modules/budgets/budgets.module';
import { PurchasesModule } from './modules/purchases/purchases.module';
import { HrModule } from './modules/hr/hr.module';
import { FinanceModule } from './modules/finance/finance.module';
import { PostSalesModule } from './modules/post-sales/post-sales.module';
import { DashboardModule } from './modules/dashboard/dashboard.module';
import { NotificationsModule } from './modules/notifications/notifications.module';

// Common
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from './common/guards/jwt-auth.guard';

@Module({
  imports: [
    // Configuration
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
    }),

    // Database
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: getDatabaseConfig,
    }),

    // Rate limiting
    ThrottlerModule.forRoot([
      {
        ttl: 60000, // 1 minuto
        limit: 100, // 100 requests por minuto
      },
    ]),

    // Scheduled tasks
    ScheduleModule.forRoot(),

    // Event emitter
    EventEmitterModule.forRoot(),

    // Feature modules
    AuthModule,
    UsersModule,
    ConstructorasModule,
    ProjectsModule,
    BudgetsModule,
    PurchasesModule,
    HrModule,
    FinanceModule,
    PostSalesModule,
    DashboardModule,
    NotificationsModule,
  ],
  providers: [
    // Global JWT guard
    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    },
  ],
})
export class AppModule {}
```

---

### 3. Frontend React + Vite - Estructura

#### Vite Configuration

**Archivo:** `apps/frontend/vite.config.ts`

```typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@/components': path.resolve(__dirname, './src/components'),
      '@/features': path.resolve(__dirname, './src/features'),
      '@/stores': path.resolve(__dirname, './src/stores'),
      '@/services': path.resolve(__dirname, './src/services'),
      '@/hooks': path.resolve(__dirname, './src/hooks'),
      '@/utils': path.resolve(__dirname, './src/utils'),
      '@/types': path.resolve(__dirname, './src/types'),
    },
  },
  server: {
    port: 5173,
    host: true,
    strictPort: true,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
      },
    },
  },
  build: {
    outDir: 'dist',
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom', 'react-router-dom'],
          'zustand-vendor': ['zustand'],
          'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
        },
      },
    },
  },
});
```

#### TypeScript Configuration

**Archivo:** `apps/frontend/tsconfig.json`

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,

    /* Path aliases */
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@/components/*": ["./src/components/*"],
      "@/features/*": ["./src/features/*"],
      "@/stores/*": ["./src/stores/*"],
      "@/services/*": ["./src/services/*"],
      "@/hooks/*": ["./src/hooks/*"],
      "@/utils/*": ["./src/utils/*"],
      "@/types/*": ["./src/types/*"]
    }
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
```

#### Main App Component

**Archivo:** `apps/frontend/src/App.tsx`

```typescript
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { Toaster } from 'sonner';

// Layouts
import { AuthLayout } from '@/components/layout/AuthLayout';
import { DashboardLayout } from '@/components/layout/DashboardLayout';

// Features
import { LoginPage } from '@/features/auth/pages/LoginPage';
import { RegisterPage } from '@/features/auth/pages/RegisterPage';
import { DashboardPage } from '@/features/dashboard/pages/DashboardPage';
import { ProjectsPage } from '@/features/projects/pages/ProjectsPage';
import { BudgetsPage } from '@/features/budgets/pages/BudgetsPage';

// Guards
import { ProtectedRoute } from '@/components/guards/ProtectedRoute';
import { RoleGuard } from '@/components/guards/RoleGuard';

// Create query client
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutos
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>
        <Routes>
          {/* Public routes */}
          <Route element={<AuthLayout />}>
            <Route path="/login" element={<LoginPage />} />
            <Route path="/register" element={<RegisterPage />} />
          </Route>

          {/* Protected routes */}
          <Route
            element={
              <ProtectedRoute>
                <DashboardLayout />
              </ProtectedRoute>
            }
          >
            <Route path="/dashboard" element={<DashboardPage />} />

            {/* Projects - Director, Engineer, Resident */}
            <Route
              path="/projects"
              element={
                <RoleGuard allowedRoles={['director', 'engineer', 'resident']}>
                  <ProjectsPage />
                </RoleGuard>
              }
            />

            {/* Budgets - Director, Engineer */}
            <Route
              path="/budgets"
              element={
                <RoleGuard allowedRoles={['director', 'engineer']}>
                  <BudgetsPage />
                </RoleGuard>
              }
            />

            {/* More routes... */}
          </Route>

          {/* Redirect */}
          <Route path="/" element={<Navigate to="/dashboard" replace />} />
          <Route path="*" element={<Navigate to="/dashboard" replace />} />
        </Routes>
      </BrowserRouter>

      {/* Toasts */}
      <Toaster position="top-right" richColors />

      {/* React Query Devtools */}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

export default App;
```

#### API Client Service

**Archivo:** `apps/frontend/src/services/api.service.ts`

```typescript
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { toast } from 'sonner';

class ApiService {
  private client: AxiosInstance;

  constructor() {
    this.client = axios.create({
      baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api',
      timeout: 30000,
      headers: {
        'Content-Type': 'application/json',
      },
    });

    this.setupInterceptors();
  }

  private setupInterceptors() {
    // Request interceptor - Agregar token
    this.client.interceptors.request.use(
      (config) => {
        const token = localStorage.getItem('accessToken');
        if (token) {
          config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
      },
      (error) => Promise.reject(error),
    );

    // Response interceptor - Manejo de errores
    this.client.interceptors.response.use(
      (response) => response,
      async (error) => {
        if (error.response?.status === 401) {
          // Token expirado o inválido
          localStorage.removeItem('accessToken');
          localStorage.removeItem('constructora-storage');
          window.location.href = '/login';
          toast.error('Sesión expirada. Por favor inicia sesión nuevamente.');
        } else if (error.response?.status === 403) {
          toast.error('No tienes permisos para realizar esta acción.');
        } else if (error.response?.status >= 500) {
          toast.error('Error del servidor. Intenta nuevamente más tarde.');
        }

        return Promise.reject(error);
      },
    );
  }

  async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response: AxiosResponse<T> = await this.client.get(url, config);
    return response.data;
  }

  async post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
    const response: AxiosResponse<T> = await this.client.post(url, data, config);
    return response.data;
  }

  async put<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
    const response: AxiosResponse<T> = await this.client.put(url, data, config);
    return response.data;
  }

  async patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
    const response: AxiosResponse<T> = await this.client.patch(url, data, config);
    return response.data;
  }

  async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response: AxiosResponse<T> = await this.client.delete(url, config);
    return response.data;
  }
}

export const apiService = new ApiService();
```

---

### 4. Docker Setup

#### Docker Compose

**Archivo:** `docker-compose.yml`

```yaml
version: '3.8'

services:
  # PostgreSQL Database
  postgres:
    image: postgres:15-alpine
    container_name: gamilit-construction-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: gamilit_user
      POSTGRES_PASSWORD: secure_password_here
      POSTGRES_DB: gamilit_construction
      PGDATA: /var/lib/postgresql/data/pgdata
    ports:
      - '5432:5432'
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./apps/database/scripts:/docker-entrypoint-initdb.d
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U gamilit_user -d gamilit_construction']
      interval: 10s
      timeout: 5s
      retries: 5

  # Backend NestJS
  backend:
    build:
      context: .
      dockerfile: ./apps/backend/Dockerfile
      target: development
    container_name: gamilit-construction-backend
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      NODE_ENV: development
      PORT: 3000
      DB_HOST: postgres
      DB_PORT: 5432
      DB_USERNAME: gamilit_user
      DB_PASSWORD: secure_password_here
      DB_DATABASE: gamilit_construction
      JWT_SECRET: your_jwt_secret_key_here
      JWT_EXPIRES_IN: 15m
    ports:
      - '3000:3000'
    volumes:
      - ./apps/backend:/app/apps/backend
      - /app/apps/backend/node_modules
      - /app/node_modules
    command: npm run start:dev

  # Frontend React
  frontend:
    build:
      context: .
      dockerfile: ./apps/frontend/Dockerfile
      target: development
    container_name: gamilit-construction-frontend
    restart: unless-stopped
    depends_on:
      - backend
    environment:
      VITE_API_URL: http://localhost:3000/api
    ports:
      - '5173:5173'
    volumes:
      - ./apps/frontend:/app/apps/frontend
      - /app/apps/frontend/node_modules
      - /app/node_modules
    command: npm run dev

volumes:
  postgres_data:
    driver: local
```

#### Backend Dockerfile

**Archivo:** `apps/backend/Dockerfile`

```dockerfile
# Development stage
FROM node:20-alpine AS development

WORKDIR /app

# Copy package files
COPY package*.json ./
COPY apps/backend/package*.json ./apps/backend/

# Install dependencies
RUN npm ci

# Copy source code
COPY . .

# Expose port
EXPOSE 3000

# Start development server
CMD ["npm", "run", "start:dev"]

# ===============================

# Production build stage
FROM node:20-alpine AS build

WORKDIR /app

COPY package*.json ./
COPY apps/backend/package*.json ./apps/backend/

RUN npm ci --only=production

COPY . .

RUN npm run build

# ===============================

# Production stage
FROM node:20-alpine AS production

WORKDIR /app

ENV NODE_ENV=production

COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package*.json ./

EXPOSE 3000

CMD ["node", "dist/main"]
```

---

### 5. Code Quality Tools

#### ESLint Configuration

**Archivo:** `.eslintrc.json`

```json
{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "project": "./tsconfig.json",
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint", "import", "prettier"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:import/recommended",
    "plugin:import/typescript",
    "prettier"
  ],
  "rules": {
    "@typescript-eslint/interface-name-prefix": "off",
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/no-unused-vars": [
      "error",
      {
        "argsIgnorePattern": "^_",
        "varsIgnorePattern": "^_"
      }
    ],
    "import/order": [
      "error",
      {
        "groups": [
          "builtin",
          "external",
          "internal",
          "parent",
          "sibling",
          "index"
        ],
        "newlines-between": "always",
        "alphabetize": {
          "order": "asc",
          "caseInsensitive": true
        }
      }
    ],
    "prettier/prettier": "error"
  },
  "settings": {
    "import/resolver": {
      "typescript": {
        "alwaysTryTypes": true
      }
    }
  }
}
```

#### Prettier Configuration

**Archivo:** `.prettierrc`

```json
{
  "semi": true,
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "arrowParens": "always",
  "endOfLine": "lf"
}
```

#### Husky Pre-commit Hook

**Archivo:** `.husky/pre-commit`

```bash
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# Run lint-staged
npx lint-staged

# Run type check
npm run type-check
```

**Archivo:** `package.json` (lint-staged config)

```json
{
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md,yml,yaml}": [
      "prettier --write"
    ]
  }
}
```

---

### 6. CI/CD Pipeline

#### GitHub Actions Workflow

**Archivo:** `.github/workflows/ci.yml`

```yaml
name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  lint-and-test:
    name: Lint and Test
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [20.x]

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_USER: gamilit_user
          POSTGRES_PASSWORD: test_password
          POSTGRES_DB: gamilit_construction_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

      - name: Run Prettier check
        run: npm run format:check

      - name: Run TypeScript type check
        run: npm run type-check

      - name: Run backend unit tests
        run: npm run test:backend
        env:
          DB_HOST: localhost
          DB_PORT: 5432
          DB_USERNAME: gamilit_user
          DB_PASSWORD: test_password
          DB_DATABASE: gamilit_construction_test
          JWT_SECRET: test_secret_key
          NODE_ENV: test

      - name: Run frontend tests
        run: npm run test:frontend

      - name: Run e2e tests
        run: npm run test:e2e
        env:
          DB_HOST: localhost
          DB_PORT: 5432
          DB_USERNAME: gamilit_user
          DB_PASSWORD: test_password
          DB_DATABASE: gamilit_construction_test
          JWT_SECRET: test_secret_key
          NODE_ENV: test

      - name: Generate coverage report
        run: npm run test:cov

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info
          flags: unittests
          name: codecov-umbrella

  build:
    name: Build
    runs-on: ubuntu-latest
    needs: lint-and-test

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20.x
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build backend
        run: npm run build:backend

      - name: Build frontend
        run: npm run build:frontend

      - name: Archive production artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-artifacts
          path: |
            apps/backend/dist
            apps/frontend/dist
```

---

## 🧪 Test Cases

### TC-INFRA-001: Instalación Limpia

**Pre-condiciones:**
- Sistema con Node.js 20+ y Docker instalados
- Puerto 5432, 3000, 5173 disponibles

**Pasos:**
1. Clonar repositorio: `git clone <repo-url>`
2. Ejecutar: `npm install`
3. Ejecutar: `docker-compose up -d postgres`
4. Esperar a que PostgreSQL esté healthy
5. Ejecutar: `npm run migrate`
6. Ejecutar: `npm run dev`

**Resultado esperado:**
- ✅ Backend corriendo en http://localhost:3000
- ✅ Frontend corriendo en http://localhost:5173
- ✅ Swagger docs en http://localhost:3000/api/docs
- ✅ No hay errores en consola

---

### TC-INFRA-002: Hot Reload Backend

**Pre-condiciones:**
- Backend corriendo en modo desarrollo

**Pasos:**
1. Abrir archivo `apps/backend/src/modules/users/users.controller.ts`
2. Modificar el mensaje de retorno de un endpoint
3. Guardar archivo
4. Observar la consola del backend

**Resultado esperado:**
- ✅ NestJS detecta el cambio
- ✅ Recompila automáticamente
- ✅ Servidor se reinicia en < 3 segundos
- ✅ Endpoint refleja el cambio sin reinicio manual

---

### TC-INFRA-003: Hot Reload Frontend

**Pre-condiciones:**
- Frontend corriendo en modo desarrollo

**Pasos:**
1. Abrir archivo `apps/frontend/src/features/dashboard/pages/DashboardPage.tsx`
2. Modificar un texto en el componente
3. Guardar archivo
4. Observar el navegador

**Resultado esperado:**
- ✅ Vite detecta el cambio
- ✅ Hot Module Replacement (HMR) se ejecuta
- ✅ Componente se actualiza sin recargar la página
- ✅ El cambio es visible instantáneamente

---

### TC-INFRA-004: Pre-commit Hooks

**Pre-condiciones:**
- Husky instalado y configurado
- Archivo con errores de lint

**Pasos:**
1. Modificar un archivo TypeScript introduciendo errores de lint:
   ```typescript
   // Agregar variable no utilizada
   const unusedVar = 'test';

   // Agregar línea sin punto y coma
   const x = 5
   ```
2. Stage el archivo: `git add .`
3. Intentar commit: `git commit -m "Test commit"`

**Resultado esperado:**
- ✅ Pre-commit hook se ejecuta
- ✅ ESLint detecta errores
- ✅ El commit es rechazado
- ✅ Se muestra mensaje con los errores encontrados

---

### TC-INFRA-005: Database Migrations

**Pre-condiciones:**
- PostgreSQL corriendo
- Backend configurado

**Pasos:**
1. Crear nueva migración: `npm run migration:create CreateTestTable`
2. Implementar migración:
   ```typescript
   public async up(queryRunner: QueryRunner): Promise<void> {
     await queryRunner.query(`
       CREATE TABLE test (
         id SERIAL PRIMARY KEY,
         name VARCHAR(255)
       );
     `);
   }
   ```
3. Ejecutar: `npm run migration:run`
4. Conectar a la base de datos y verificar

**Resultado esperado:**
- ✅ Migración se crea correctamente
- ✅ Migración se ejecuta sin errores
- ✅ Tabla `test` existe en la base de datos
- ✅ Entry en tabla `migrations` registra la ejecución

---

### TC-INFRA-006: CI Pipeline

**Pre-condiciones:**
- Código pusheado a GitHub
- GitHub Actions configurado

**Pasos:**
1. Crear PR con cambios
2. Observar GitHub Actions

**Resultado esperado:**
- ✅ Pipeline de CI se ejecuta automáticamente
- ✅ Job de lint pasa
- ✅ Job de type-check pasa
- ✅ Job de tests pasa
- ✅ Job de build pasa
- ✅ Reporte de cobertura se genera
- ✅ PR muestra status check verde

---

### TC-INFRA-007: RLS Context Injection

**Pre-condiciones:**
- Backend corriendo
- Usuario autenticado
- Constructora activa

**Pasos:**
1. Hacer request a cualquier endpoint protegido con token JWT válido
2. Inspeccionar logs de PostgreSQL (query log habilitado)
3. Verificar que se ejecuten los `set_config` antes de la query principal

**Resultado esperado:**
- ✅ `set_config('app.current_user_id', '...', true)` se ejecuta
- ✅ `set_config('app.current_constructora_id', '...', true)` se ejecuta
- ✅ `set_config('app.current_user_role', '...', true)` se ejecuta
- ✅ Query principal usa estas variables en RLS policies

---

### TC-INFRA-008: Swagger Documentation

**Pre-condiciones:**
- Backend corriendo en modo desarrollo

**Pasos:**
1. Abrir navegador en http://localhost:3000/api/docs
2. Explorar endpoints documentados
3. Intentar ejecutar endpoint `/api/auth/login` desde Swagger UI

**Resultado esperado:**
- ✅ Swagger UI carga correctamente
- ✅ Todos los módulos están listados con sus tags
- ✅ Schemas de DTOs están documentados
- ✅ Es posible ejecutar requests desde la UI
- ✅ Bearer token puede configurarse para endpoints protegidos

---

## 📋 Tareas de Implementación

### Sprint 0 - Semana 1

#### Backend

- [ ] **INFRA-BE-001:** Crear estructura de carpetas del proyecto NestJS
  - Estimado: 1h
  - Asignado a: Backend Lead

- [ ] **INFRA-BE-002:** Configurar TypeORM con migraciones
  - Estimado: 2h
  - Asignado a: Backend Lead

- [ ] **INFRA-BE-003:** Crear script de inicialización de base de datos
  - Estimado: 2h
  - Asignado a: DevOps

- [ ] **INFRA-BE-004:** Implementar main.ts con configuración completa
  - Estimado: 2h
  - Asignado a: Backend Lead

- [ ] **INFRA-BE-005:** Crear módulos base (Auth, Users, Constructoras)
  - Estimado: 3h
  - Asignado a: Backend Dev

- [ ] **INFRA-BE-006:** Configurar Swagger documentation
  - Estimado: 1h
  - Asignado a: Backend Dev

- [ ] **INFRA-BE-007:** Implementar guards globales (JWT, Roles)
  - Estimado: 2h
  - Asignado a: Backend Dev

- [ ] **INFRA-BE-008:** Crear interceptors (Logging, Transform, RLS Context)
  - Estimado: 2h
  - Asignado a: Backend Dev

#### Frontend

- [ ] **INFRA-FE-001:** Crear estructura de carpetas del proyecto React
  - Estimado: 1h
  - Asignado a: Frontend Lead

- [ ] **INFRA-FE-002:** Configurar Vite con path aliases
  - Estimado: 1h
  - Asignado a: Frontend Lead

- [ ] **INFRA-FE-003:** Configurar React Router con layouts
  - Estimado: 2h
  - Asignado a: Frontend Dev

- [ ] **INFRA-FE-004:** Implementar API service con interceptors
  - Estimado: 2h
  - Asignado a: Frontend Dev

- [ ] **INFRA-FE-005:** Crear guards de navegación (ProtectedRoute, RoleGuard)
  - Estimado: 2h
  - Asignado a: Frontend Dev

- [ ] **INFRA-FE-006:** Configurar React Query
  - Estimado: 1h
  - Asignado a: Frontend Dev

- [ ] **INFRA-FE-007:** Crear componentes de layout base
  - Estimado: 2h
  - Asignado a: Frontend Dev

#### DevOps

- [ ] **INFRA-DO-001:** Configurar Docker Compose para desarrollo
  - Estimado: 2h
  - Asignado a: DevOps

- [ ] **INFRA-DO-002:** Crear Dockerfiles (backend y frontend)
  - Estimado: 2h
  - Asignado a: DevOps

- [ ] **INFRA-DO-003:** Configurar variables de entorno (.env templates)
  - Estimado: 1h
  - Asignado a: DevOps

- [ ] **INFRA-DO-004:** Configurar GitHub Actions CI pipeline
  - Estimado: 3h
  - Asignado a: DevOps

#### Code Quality

- [ ] **INFRA-CQ-001:** Configurar ESLint para backend y frontend
  - Estimado: 1h
  - Asignado a: Tech Lead

- [ ] **INFRA-CQ-002:** Configurar Prettier
  - Estimado: 0.5h
  - Asignado a: Tech Lead

- [ ] **INFRA-CQ-003:** Configurar Husky + lint-staged
  - Estimado: 1h
  - Asignado a: Tech Lead

- [ ] **INFRA-CQ-004:** Configurar Jest para testing (backend y frontend)
  - Estimado: 2h
  - Asignado a: QA Lead

#### Documentation

- [ ] **INFRA-DOC-001:** Crear README.md con instrucciones de instalación
  - Estimado: 1h
  - Asignado a: Tech Lead

- [ ] **INFRA-DOC-002:** Documentar estructura de carpetas
  - Estimado: 1h
  - Asignado a: Tech Lead

- [ ] **INFRA-DOC-003:** Crear CONTRIBUTING.md con guía de desarrollo
  - Estimado: 1h
  - Asignado a: Tech Lead

**Total estimado:** ~40 horas (distribuidas en equipo de 4 devs = 2 semanas)

---

## 🔗 Dependencias

### Dependencias Externas

- **Ninguna** (esta es la base del proyecto)

### Bloqueantes para

- ✅ Todas las demás historias de usuario
- ✅ MAI-002 (Gestión de Proyectos)
- ✅ MAI-003 (Gestión de Presupuestos)
- ✅ MAI-004 (Gestión de Compras)
- ✅ MAI-005 (Gamificación)
- ✅ MAI-006 (RRHH)

---

## 📊 Definición de Hecho (DoD)

- ✅ Backend ejecutable localmente en < 5 minutos
- ✅ Frontend ejecutable localmente en < 5 minutos
- ✅ Base de datos PostgreSQL configurada con schemas
- ✅ Migrations funcionando correctamente
- ✅ Hot reload funcional en backend y frontend
- ✅ Docker Compose levanta todos los servicios
- ✅ ESLint + Prettier configurados y funcionando
- ✅ Pre-commit hooks funcionando
- ✅ CI pipeline ejecutándose en GitHub Actions
- ✅ Swagger documentation accesible en `/api/docs`
- ✅ Todos los test cases (TC-INFRA-001 a TC-INFRA-008) pasan
- ✅ README.md con instrucciones de instalación completas
- ✅ Variables de entorno documentadas (`.env.example`)
- ✅ No hay warnings ni errores en consola (dev mode)

---

## 📝 Notas Adicionales

### Performance Targets

- **Backend startup:** < 5 segundos
- **Frontend startup:** < 3 segundos
- **Hot reload backend:** < 3 segundos
- **Hot reload frontend:** < 1 segundo
- **Build backend:** < 30 segundos
- **Build frontend:** < 20 segundos

### Security Considerations

- ✅ No commits de archivos `.env` (usar `.env.example`)
- ✅ Secrets en variables de entorno, no hardcoded
- ✅ PostgreSQL password fuerte en producción
- ✅ JWT secret diferente por ambiente
- ✅ Helmet configurado para seguridad HTTP
- ✅ CORS configurado restrictivamente

### Monitoring & Logging

- ✅ Winston logger configurado (backend)
- ✅ Request logging con timestamps
- ✅ Error logging con stack traces
- ✅ Query logging en desarrollo (deshabilitado en prod)

---

## 🎓 Lecciones de GAMILIT

### Qué Reutilizar (80%)

✅ **Estructura completa del proyecto:**
- Organización de carpetas backend/frontend
- Configuración de TypeORM
- Guards, decorators, interceptors
- API service con interceptors
- Docker setup

✅ **Herramientas de desarrollo:**
- ESLint + Prettier config
- Husky hooks
- GitHub Actions CI
- Swagger configuration

### Qué Adaptar (20%)

🔄 **Schemas de base de datos:**
- GAMILIT: `auth_management`, `gamification_system`, `educational_content`
- Construcción: `auth_management`, `projects`, `budgets`, `purchases`, `hr`, `finance`

🔄 **Módulos de negocio:**
- GAMILIT: Students, Teachers, Courses
- Construcción: Projects, Budgets, Employees

---

**Fecha de creación:** 2025-11-17
**Última actualización:** 2025-11-17
**Versión:** 1.0
