# 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 ```tsx // 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 = memo(({ project, onClick, showProgress = true }) => { return ( onClick?.(project.id)} >

{project.name}

{project.code}

{project.status}
{showProgress && (
{project.progressPercentage}% completado
)}
); }); ProjectCard.displayName = 'ProjectCard'; ``` ### 3. Hook Pattern (TanStack Query) ```tsx // 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 ```tsx // 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> => { const response = await apiClient.get(BASE_URL, { params: query }); return response.data; }, findOne: async (id: string): Promise => { const response = await apiClient.get(`${BASE_URL}/${id}`); return response.data; }, create: async (dto: CreateProjectDto): Promise => { const response = await apiClient.post(BASE_URL, dto); return response.data; }, update: async (id: string, dto: UpdateProjectDto): Promise => { const response = await apiClient.put(`${BASE_URL}/${id}`, dto); return response.data; }, delete: async (id: string): Promise => { await apiClient.delete(`${BASE_URL}/${id}`); }, updateStatus: async (id: string, status: ProjectStatus): Promise => { const response = await apiClient.put(`${BASE_URL}/${id}/status`, { status }); return response.data; } }; ``` ### 5. Store Pattern (Zustand) ```tsx // 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) => void; resetFilters: () => void; setViewMode: (mode: 'list' | 'grid' | 'kanban') => void; } const initialFilters: ProjectFilters = { status: undefined, search: '', dateRange: undefined, }; export const useConstructionStore = create()( 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 ```tsx // 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 ; if (error) return ; return (
setIsCreateModalOpen(true)}> + Nuevo Proyecto } />
setFilters({ search })} placeholder="Buscar proyectos..." />
{data?.items.length === 0 ? ( setIsCreateModalOpen(true)}> Crear primer proyecto } /> ) : ( <> {viewMode === 'list' && ( )} {viewMode === 'grid' && ( )} {viewMode === 'kanban' && ( )} setFilters({ page })} /> )} setIsCreateModalOpen(false)} onSubmit={handleCreateProject} isLoading={createProject.isPending} />
); }; ``` --- ## 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](components/COMP-shared.md) | - | Documentado | | construction | [COMP-construction.md](components/COMP-construction.md) | [PAGES-construction.md](pages/PAGES-construction.md) | Documentado | | compliance | [COMP-compliance.md](components/COMP-compliance.md) | [PAGES-compliance.md](pages/PAGES-compliance.md) | Documentado | | finance | [COMP-finance.md](components/COMP-finance.md) | [PAGES-finance.md](pages/PAGES-finance.md) | Documentado | | assets | [COMP-assets.md](components/COMP-assets.md) | [PAGES-assets.md](pages/PAGES-assets.md) | Documentado | | documents | [COMP-documents.md](components/COMP-documents.md) | [PAGES-documents.md](pages/PAGES-documents.md) | Documentado | --- ## Routing ### Estructura de Rutas ```tsx // 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: , children: [ // Dashboard { index: true, element: }, // Construction { path: 'projects', element: }, { path: 'projects/:id', element: }, { path: 'projects/:id/budget', element: }, { path: 'projects/:id/progress', element: }, { path: 'developments', element: }, { path: 'developments/:id', element: }, // Compliance { path: 'compliance', element: }, { path: 'compliance/programs', element: }, { path: 'compliance/project/:id', element: }, { path: 'compliance/audits', element: }, { path: 'compliance/audits/:id', element: }, // Finance { path: 'finance', element: }, { path: 'finance/accounting', element: }, { path: 'finance/payables', element: }, { path: 'finance/receivables', element: }, { path: 'finance/cash-flow', element: }, { path: 'finance/bank', element: }, { path: 'finance/reports', element: }, // Assets { path: 'assets', element: }, { path: 'assets/:id', element: }, { path: 'assets/maintenance', element: }, { path: 'assets/work-orders', element: }, { path: 'assets/tracking', element: }, // Documents { path: 'documents', element: }, { path: 'documents/folder/:id', element: }, { path: 'documents/plans', element: }, { path: 'documents/approvals', element: }, // Settings { path: 'settings', element: }, ], }, { path: '/auth', element: , children: [ { path: 'login', element: }, { path: 'forgot-password', element: }, { path: 'reset-password', element: }, ], }, ]); ``` --- ## 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 - [Backend Specifications](../05-backend-specs/) - [Domain Models](../04-modelado/domain-models/) - [Epicas](../08-epicas/) --- *Ultima actualizacion: 2025-12-05*