workspace/projects/erp-suite/apps/verticales/construccion/docs/06-frontend-specs
rckrdmrd ea1879f4ad feat: Initial workspace structure with multi-level Git configuration
- Configure workspace Git repository with comprehensive .gitignore
- Add Odoo as submodule for ERP reference code
- Include documentation: SETUP.md, GIT-STRUCTURE.md
- Add gitignore templates for projects (backend, frontend, database)
- Structure supports independent repos per project/subproject level

Workspace includes:
- core/ - Reusable patterns, modules, orchestration system
- projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.)
- knowledge-base/ - Reference code and patterns (includes Odoo submodule)
- devtools/ - Development tools and templates
- customers/ - Client implementations template

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 10:44:23 -06:00
..
components feat: Initial workspace structure with multi-level Git configuration 2025-12-08 10:44:23 -06:00
pages feat: Initial workspace structure with multi-level Git configuration 2025-12-08 10:44:23 -06:00
stores feat: Initial workspace structure with multi-level Git configuration 2025-12-08 10:44:23 -06:00
README.md feat: Initial workspace structure with multi-level Git configuration 2025-12-08 10:44:23 -06:00

Frontend Specifications - ERP Construccion

Fecha: 2025-12-05 Version: 1.0.0 Stack: React 18 / TypeScript 5.3+ / Vite / Zustand / TanStack Query / TailwindCSS


Estructura del Directorio

06-frontend-specs/
├── README.md (este archivo)
├── components/
│   ├── COMP-shared.md          [Componentes compartidos]
│   ├── COMP-construction.md    [Componentes de construccion]
│   ├── COMP-compliance.md      [Componentes de cumplimiento]
│   ├── COMP-finance.md         [Componentes financieros]
│   ├── COMP-assets.md          [Componentes de activos]
│   └── COMP-documents.md       [Componentes DMS]
├── pages/
│   ├── PAGES-construction.md   [Paginas de proyectos]
│   ├── PAGES-compliance.md     [Paginas de cumplimiento]
│   ├── PAGES-finance.md        [Paginas financieras]
│   ├── PAGES-assets.md         [Paginas de activos]
│   └── PAGES-documents.md      [Paginas de documentos]
└── stores/
    └── STORES-spec.md          [Especificacion de stores]

Arquitectura Frontend

Estructura de Aplicacion

apps/frontend/src/
├── app/
│   ├── App.tsx
│   ├── router.tsx
│   └── providers.tsx
├── features/
│   ├── construction/
│   │   ├── components/
│   │   ├── pages/
│   │   ├── hooks/
│   │   ├── api/
│   │   └── store/
│   ├── compliance/
│   ├── finance/
│   ├── assets/
│   └── documents/
├── shared/
│   ├── components/
│   │   ├── ui/
│   │   ├── forms/
│   │   ├── tables/
│   │   └── charts/
│   ├── hooks/
│   ├── utils/
│   ├── types/
│   └── api/
├── layouts/
│   ├── MainLayout.tsx
│   ├── AuthLayout.tsx
│   └── components/
└── styles/

Patrones de Desarrollo

1. Feature-Based Structure

Cada modulo sigue una estructura consistente:

features/construction/
├── components/
│   ├── ProjectCard.tsx
│   ├── ProjectList.tsx
│   ├── BudgetForm.tsx
│   └── ProgressChart.tsx
├── pages/
│   ├── ProjectsPage.tsx
│   ├── ProjectDetailPage.tsx
│   ├── BudgetPage.tsx
│   └── ProgressPage.tsx
├── hooks/
│   ├── useProjects.ts
│   ├── useBudget.ts
│   └── useProgress.ts
├── api/
│   ├── projects.api.ts
│   ├── budgets.api.ts
│   └── progress.api.ts
├── store/
│   └── construction.store.ts
├── types/
│   └── construction.types.ts
└── index.ts

2. Component Pattern

// components/ProjectCard.tsx
import { FC, memo } from 'react';
import { Card, Badge, Progress } from '@/shared/components/ui';
import { Project } from '../types/construction.types';

interface ProjectCardProps {
  project: Project;
  onClick?: (id: string) => void;
  showProgress?: boolean;
}

export const ProjectCard: FC<ProjectCardProps> = memo(({
  project,
  onClick,
  showProgress = true
}) => {
  return (
    <Card
      className="p-4 hover:shadow-lg transition-shadow cursor-pointer"
      onClick={() => onClick?.(project.id)}
    >
      <div className="flex justify-between items-start">
        <div>
          <h3 className="font-semibold text-lg">{project.name}</h3>
          <p className="text-sm text-gray-500">{project.code}</p>
        </div>
        <Badge variant={getStatusVariant(project.status)}>
          {project.status}
        </Badge>
      </div>

      {showProgress && (
        <div className="mt-4">
          <Progress value={project.progressPercentage} />
          <span className="text-sm text-gray-600">
            {project.progressPercentage}% completado
          </span>
        </div>
      )}
    </Card>
  );
});

ProjectCard.displayName = 'ProjectCard';

3. Hook Pattern (TanStack Query)

// hooks/useProjects.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { projectsApi } from '../api/projects.api';
import { CreateProjectDto, ProjectQueryDto } from '../types/construction.types';

const PROJECTS_KEY = ['projects'];

export function useProjects(query?: ProjectQueryDto) {
  return useQuery({
    queryKey: [...PROJECTS_KEY, query],
    queryFn: () => projectsApi.findAll(query),
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
}

export function useProject(id: string) {
  return useQuery({
    queryKey: [...PROJECTS_KEY, id],
    queryFn: () => projectsApi.findOne(id),
    enabled: !!id,
  });
}

export function useCreateProject() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (dto: CreateProjectDto) => projectsApi.create(dto),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: PROJECTS_KEY });
    },
  });
}

export function useUpdateProject() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({ id, dto }: { id: string; dto: UpdateProjectDto }) =>
      projectsApi.update(id, dto),
    onSuccess: (_, { id }) => {
      queryClient.invalidateQueries({ queryKey: [...PROJECTS_KEY, id] });
      queryClient.invalidateQueries({ queryKey: PROJECTS_KEY });
    },
  });
}

export function useDeleteProject() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (id: string) => projectsApi.delete(id),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: PROJECTS_KEY });
    },
  });
}

4. API Layer Pattern

// api/projects.api.ts
import { apiClient } from '@/shared/api/client';
import { PaginatedResponse } from '@/shared/types';
import {
  Project,
  ProjectDetail,
  CreateProjectDto,
  UpdateProjectDto,
  ProjectQueryDto
} from '../types/construction.types';

const BASE_URL = '/api/v1/projects';

export const projectsApi = {
  findAll: async (query?: ProjectQueryDto): Promise<PaginatedResponse<Project>> => {
    const response = await apiClient.get(BASE_URL, { params: query });
    return response.data;
  },

  findOne: async (id: string): Promise<ProjectDetail> => {
    const response = await apiClient.get(`${BASE_URL}/${id}`);
    return response.data;
  },

  create: async (dto: CreateProjectDto): Promise<Project> => {
    const response = await apiClient.post(BASE_URL, dto);
    return response.data;
  },

  update: async (id: string, dto: UpdateProjectDto): Promise<Project> => {
    const response = await apiClient.put(`${BASE_URL}/${id}`, dto);
    return response.data;
  },

  delete: async (id: string): Promise<void> => {
    await apiClient.delete(`${BASE_URL}/${id}`);
  },

  updateStatus: async (id: string, status: ProjectStatus): Promise<Project> => {
    const response = await apiClient.put(`${BASE_URL}/${id}/status`, { status });
    return response.data;
  }
};

5. Store Pattern (Zustand)

// store/construction.store.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { Project, ProjectFilters } from '../types/construction.types';

interface ConstructionState {
  // State
  selectedProjectId: string | null;
  filters: ProjectFilters;
  viewMode: 'list' | 'grid' | 'kanban';

  // Actions
  setSelectedProject: (id: string | null) => void;
  setFilters: (filters: Partial<ProjectFilters>) => void;
  resetFilters: () => void;
  setViewMode: (mode: 'list' | 'grid' | 'kanban') => void;
}

const initialFilters: ProjectFilters = {
  status: undefined,
  search: '',
  dateRange: undefined,
};

export const useConstructionStore = create<ConstructionState>()(
  devtools(
    persist(
      (set) => ({
        // Initial State
        selectedProjectId: null,
        filters: initialFilters,
        viewMode: 'list',

        // Actions
        setSelectedProject: (id) =>
          set({ selectedProjectId: id }, false, 'setSelectedProject'),

        setFilters: (filters) =>
          set(
            (state) => ({ filters: { ...state.filters, ...filters } }),
            false,
            'setFilters'
          ),

        resetFilters: () =>
          set({ filters: initialFilters }, false, 'resetFilters'),

        setViewMode: (mode) =>
          set({ viewMode: mode }, false, 'setViewMode'),
      }),
      {
        name: 'construction-store',
        partialize: (state) => ({ viewMode: state.viewMode }),
      }
    ),
    { name: 'ConstructionStore' }
  )
);

6. Page Pattern

// pages/ProjectsPage.tsx
import { FC, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
  PageHeader,
  SearchInput,
  FilterBar,
  Pagination,
  LoadingSpinner,
  ErrorMessage,
  EmptyState
} from '@/shared/components';
import { useProjects, useCreateProject } from '../hooks/useProjects';
import { useConstructionStore } from '../store/construction.store';
import { ProjectList, ProjectGrid, ProjectKanban } from '../components';
import { CreateProjectModal } from '../components/CreateProjectModal';

export const ProjectsPage: FC = () => {
  const navigate = useNavigate();
  const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);

  const { filters, viewMode, setFilters, setViewMode } = useConstructionStore();
  const { data, isLoading, error, refetch } = useProjects(filters);
  const createProject = useCreateProject();

  const handleProjectClick = (id: string) => {
    navigate(`/projects/${id}`);
  };

  const handleCreateProject = async (dto: CreateProjectDto) => {
    await createProject.mutateAsync(dto);
    setIsCreateModalOpen(false);
  };

  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} onRetry={refetch} />;

  return (
    <div className="space-y-6">
      <PageHeader
        title="Proyectos"
        subtitle={`${data?.total || 0} proyectos encontrados`}
        actions={
          <Button onClick={() => setIsCreateModalOpen(true)}>
            + Nuevo Proyecto
          </Button>
        }
      />

      <div className="flex gap-4 items-center">
        <SearchInput
          value={filters.search}
          onChange={(search) => setFilters({ search })}
          placeholder="Buscar proyectos..."
        />
        <FilterBar
          filters={filters}
          onChange={setFilters}
          options={filterOptions}
        />
        <ViewToggle value={viewMode} onChange={setViewMode} />
      </div>

      {data?.items.length === 0 ? (
        <EmptyState
          title="Sin proyectos"
          description="No hay proyectos que coincidan con los filtros"
          action={
            <Button onClick={() => setIsCreateModalOpen(true)}>
              Crear primer proyecto
            </Button>
          }
        />
      ) : (
        <>
          {viewMode === 'list' && (
            <ProjectList projects={data.items} onClick={handleProjectClick} />
          )}
          {viewMode === 'grid' && (
            <ProjectGrid projects={data.items} onClick={handleProjectClick} />
          )}
          {viewMode === 'kanban' && (
            <ProjectKanban projects={data.items} onClick={handleProjectClick} />
          )}

          <Pagination
            total={data.total}
            page={filters.page}
            limit={filters.limit}
            onChange={(page) => setFilters({ page })}
          />
        </>
      )}

      <CreateProjectModal
        isOpen={isCreateModalOpen}
        onClose={() => setIsCreateModalOpen(false)}
        onSubmit={handleCreateProject}
        isLoading={createProject.isPending}
      />
    </div>
  );
};

Componentes Compartidos

UI Components

Componente Descripcion Props
Button Boton con variantes variant, size, disabled, loading
Card Contenedor con sombra className, onClick, children
Badge Etiqueta de estado variant, size, children
Modal Dialogo modal isOpen, onClose, title, children
Tooltip Tooltip informativo content, position, children
Dropdown Menu desplegable items, trigger, onSelect
Avatar Avatar de usuario src, name, size
Progress Barra de progreso value, max, variant
Spinner Indicador de carga size, color
Alert Mensaje de alerta type, title, message, onClose

Form Components

Componente Descripcion Props
Input Campo de texto label, error, helper, ...inputProps
Select Selector options, label, error, onChange
Checkbox Casilla de verificacion label, checked, onChange
Radio Boton de radio options, value, onChange
DatePicker Selector de fecha value, onChange, format
FileUpload Carga de archivos accept, maxSize, onUpload
TextArea Area de texto label, rows, maxLength
NumberInput Campo numerico min, max, step, format
CurrencyInput Campo de moneda currency, locale, onChange

Table Components

Componente Descripcion Props
DataTable Tabla de datos columns, data, onSort, onFilter
TablePagination Paginacion de tabla total, page, limit, onChange
TableFilters Filtros de tabla filters, onChange
TableActions Acciones de fila actions, row
ExportButton Exportar datos formats, onExport

Chart Components

Componente Descripcion Props
LineChart Grafico de lineas data, xKey, yKey, options
BarChart Grafico de barras data, xKey, yKey, options
PieChart Grafico circular data, nameKey, valueKey
GaugeChart Indicador circular value, max, thresholds
AreaChart Grafico de area data, xKey, yKey, options

Modulos Frontend

Modulo Spec Componentes Spec Paginas Estado
shared COMP-shared.md - Documentado
construction COMP-construction.md PAGES-construction.md Documentado
compliance COMP-compliance.md PAGES-compliance.md Documentado
finance COMP-finance.md PAGES-finance.md Documentado
assets COMP-assets.md PAGES-assets.md Documentado
documents COMP-documents.md PAGES-documents.md Documentado

Routing

Estructura de Rutas

// app/router.tsx
import { createBrowserRouter } from 'react-router-dom';
import { MainLayout } from '@/layouts/MainLayout';
import { AuthLayout } from '@/layouts/AuthLayout';

export const router = createBrowserRouter([
  {
    path: '/',
    element: <MainLayout />,
    children: [
      // Dashboard
      { index: true, element: <DashboardPage /> },

      // Construction
      { path: 'projects', element: <ProjectsPage /> },
      { path: 'projects/:id', element: <ProjectDetailPage /> },
      { path: 'projects/:id/budget', element: <BudgetPage /> },
      { path: 'projects/:id/progress', element: <ProgressPage /> },
      { path: 'developments', element: <DevelopmentsPage /> },
      { path: 'developments/:id', element: <DevelopmentDetailPage /> },

      // Compliance
      { path: 'compliance', element: <ComplianceDashboardPage /> },
      { path: 'compliance/programs', element: <ProgramsPage /> },
      { path: 'compliance/project/:id', element: <ProjectCompliancePage /> },
      { path: 'compliance/audits', element: <AuditsPage /> },
      { path: 'compliance/audits/:id', element: <AuditDetailPage /> },

      // Finance
      { path: 'finance', element: <FinanceDashboardPage /> },
      { path: 'finance/accounting', element: <AccountingPage /> },
      { path: 'finance/payables', element: <PayablesPage /> },
      { path: 'finance/receivables', element: <ReceivablesPage /> },
      { path: 'finance/cash-flow', element: <CashFlowPage /> },
      { path: 'finance/bank', element: <BankReconciliationPage /> },
      { path: 'finance/reports', element: <FinanceReportsPage /> },

      // Assets
      { path: 'assets', element: <AssetsPage /> },
      { path: 'assets/:id', element: <AssetDetailPage /> },
      { path: 'assets/maintenance', element: <MaintenancePage /> },
      { path: 'assets/work-orders', element: <WorkOrdersPage /> },
      { path: 'assets/tracking', element: <TrackingMapPage /> },

      // Documents
      { path: 'documents', element: <DocumentsPage /> },
      { path: 'documents/folder/:id', element: <FolderPage /> },
      { path: 'documents/plans', element: <PlansPage /> },
      { path: 'documents/approvals', element: <ApprovalsPage /> },

      // Settings
      { path: 'settings', element: <SettingsPage /> },
    ],
  },
  {
    path: '/auth',
    element: <AuthLayout />,
    children: [
      { path: 'login', element: <LoginPage /> },
      { path: 'forgot-password', element: <ForgotPasswordPage /> },
      { path: 'reset-password', element: <ResetPasswordPage /> },
    ],
  },
]);

State Management

Global State (Zustand)

stores/
├── auth.store.ts        // Autenticacion y usuario actual
├── tenant.store.ts      // Contexto de tenant
├── ui.store.ts          // Estado de UI (sidebar, theme, etc.)
├── notifications.store.ts // Notificaciones
└── preferences.store.ts // Preferencias de usuario

Server State (TanStack Query)

  • Datos de servidor manejados por TanStack Query
  • Cache automatico con invalidacion
  • Prefetching para navegacion rapida
  • Optimistic updates para mejor UX

Referencias


Ultima actualizacion: 2025-12-05