Initial commit - erp-construccion-frontend-web

This commit is contained in:
rckrdmrd 2026-01-04 06:38:53 -06:00
commit 0af6d41e4b
19 changed files with 19671 additions and 0 deletions

37
mobile/App.tsx Normal file
View File

@ -0,0 +1,37 @@
/**
* App Component - React Native + Expo
* MVP Sistema Administración de Obra e INFONAVIT
*/
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>🏗 Sistema Administración de Obra</Text>
<Text style={styles.subtitle}>App Móvil - Supervisor</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 8,
textAlign: 'center',
},
subtitle: {
fontSize: 16,
color: '#666',
},
});

43
mobile/README.md Normal file
View File

@ -0,0 +1,43 @@
# Frontend Mobile - MVP Sistema Administración de Obra
**Stack:** React Native + Expo
**Versión:** 1.0.0
**Fecha:** 2025-11-20
---
## 📋 DESCRIPCIÓN
Aplicación móvil para supervisores de obra.
**Funcionalidades principales:**
- Registro de avances
- Captura fotográfica
- Gestión de materiales en sitio
- Reportes rápidos
---
## 🚀 SETUP
```bash
npm install
npm start
```
Luego escanea el QR con Expo Go app.
---
## 📝 SCRIPTS
| Script | Descripción |
|--------|-------------|
| `npm start` | Inicia Expo |
| `npm run android` | Abre en Android |
| `npm run ios` | Abre en iOS |
---
**Mantenido por:** Frontend-Agent
**Última actualización:** 2025-11-20

32
mobile/app.json Normal file
View File

@ -0,0 +1,32 @@
{
"expo": {
"name": "Sistema Obra - Mobile",
"slug": "construccion-mvp-mobile",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.construccion.mvp"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.construccion.mvp"
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}

14386
mobile/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
mobile/package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "@construccion-mvp/frontend-mobile",
"version": "1.0.0",
"description": "Frontend Mobile - MVP Sistema Administración de Obra e INFONAVIT",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"expo": "~50.0.0",
"expo-status-bar": "~1.11.1",
"react": "18.2.0",
"react-native": "0.73.0",
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.17",
"zustand": "^4.4.7",
"axios": "^1.6.2"
},
"devDependencies": {
"@babel/core": "^7.23.5",
"@types/react": "~18.2.45",
"typescript": "^5.1.3"
},
"private": true
}

14
mobile/tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"@/*": ["./src/*"],
"@screens/*": ["./src/screens/*"],
"@components/*": ["./src/components/*"],
"@stores/*": ["./src/stores/*"],
"@services/*": ["./src/services/*"],
"@utils/*": ["./src/utils/*"]
}
}
}

65
web/Dockerfile Normal file
View File

@ -0,0 +1,65 @@
# =============================================================================
# Dockerfile - Frontend Web
# ERP Construccion - React + Vite + TypeScript
# =============================================================================
# -----------------------------------------------------------------------------
# Stage 1: Base
# -----------------------------------------------------------------------------
FROM node:20-alpine AS base
WORKDIR /app
# Copy package files
COPY package*.json ./
# -----------------------------------------------------------------------------
# Stage 2: Development
# -----------------------------------------------------------------------------
FROM base AS development
# Install all dependencies
RUN npm ci
# Copy source code
COPY . .
# Expose Vite dev server port
EXPOSE 5173
# Development command with hot reload
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
# -----------------------------------------------------------------------------
# Stage 3: Builder
# -----------------------------------------------------------------------------
FROM base AS builder
# Install all dependencies
RUN npm ci
# Copy source code
COPY . .
# Build for production
RUN npm run build
# -----------------------------------------------------------------------------
# Stage 4: Production (with nginx)
# -----------------------------------------------------------------------------
FROM nginx:alpine AS production
# Copy nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built files
COPY --from=builder /app/dist /usr/share/nginx/html
# Expose port
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
CMD ["nginx", "-g", "daemon off;"]

107
web/README.md Normal file
View File

@ -0,0 +1,107 @@
# Frontend Web - MVP Sistema Administración de Obra
**Stack:** React 18 + Vite + TypeScript + Zustand
**Versión:** 1.0.0
**Fecha:** 2025-11-20
---
## 📋 DESCRIPCIÓN
Aplicación web del sistema de administración de obra e INFONAVIT.
**Portales incluidos:**
- **Admin:** Portal administrativo completo
- **Supervisor:** Portal para supervisores de obra
- **Obra:** Portal para personal en sitio
---
## 🚀 SETUP INICIAL
```bash
# Instalar dependencias
npm install
# Iniciar servidor de desarrollo
npm run dev
# Build para producción
npm run build
```
La aplicación estará disponible en `http://localhost:5173`
---
## 🏗️ ESTRUCTURA
```
src/
├── shared/ # Código compartido entre portales
│ ├── components/
│ │ ├── ui/ # Componentes UI base (Button, Input, etc.)
│ │ └── layout/ # Componentes de layout (Header, Sidebar, etc.)
│ ├── hooks/ # Custom hooks
│ ├── stores/ # Zustand stores
│ ├── services/ # API services
│ ├── types/ # TypeScript types
│ ├── utils/ # Utilities
│ └── constants/ # Constantes
└── apps/ # Portales específicos
├── admin/ # Portal administrador
│ ├── pages/
│ ├── components/
│ └── routes.tsx
├── supervisor/ # Portal supervisor
└── obra/ # Portal obra
```
---
## 📝 SCRIPTS
| Script | Descripción |
|--------|-------------|
| `npm run dev` | Inicia servidor de desarrollo (port 5173) |
| `npm run build` | Build para producción |
| `npm run preview` | Preview del build de producción |
| `npm run lint` | Ejecuta ESLint |
| `npm run lint:fix` | Ejecuta ESLint y corrige |
| `npm run type-check` | Verifica tipos TypeScript |
---
## 🎨 CONVENCIONES
### Nomenclatura
Seguir **ESTANDARES-NOMENCLATURA.md**:
- Componentes: `PascalCase.tsx`
- Páginas: `PascalCasePage.tsx`
- Hooks: `useCamelCase.ts`
- Stores: `camelCase.store.ts`
- Tipos: `camelCase.types.ts`
### Path Aliases
```typescript
import { Button } from '@components/ui/Button';
import { useAuth } from '@hooks/useAuth';
import { projectStore } from '@stores/project.store';
import type { Project } from '@types/project.types';
```
---
## 📚 REFERENCIAS
- [Vite Documentation](https://vitejs.dev/)
- [React Documentation](https://react.dev/)
- [Zustand Documentation](https://zustand-demo.pmnd.rs/)
- [ESTANDARES-NOMENCLATURA.md](../../orchestration/directivas/ESTANDARES-NOMENCLATURA.md)
---
**Mantenido por:** Frontend-Agent
**Última actualización:** 2025-11-20

14
web/index.html Normal file
View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="MVP Sistema Administración de Obra e INFONAVIT" />
<title>Sistema Administración de Obra - MVP</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

47
web/nginx.conf Normal file
View File

@ -0,0 +1,47 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA routing - send all requests to index.html
location / {
try_files $uri $uri/ /index.html;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# API proxy (optional - if needed)
# location /api {
# proxy_pass http://backend:3000;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection 'upgrade';
# proxy_set_header Host $host;
# proxy_cache_bypass $http_upgrade;
# }
}

4552
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

46
web/package.json Normal file
View File

@ -0,0 +1,46 @@
{
"name": "@construccion-mvp/frontend-web",
"version": "1.0.0",
"description": "Frontend Web - MVP Sistema Administración de Obra e INFONAVIT",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext ts,tsx --fix",
"type-check": "tsc --noEmit"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.1",
"zustand": "^4.4.7",
"axios": "^1.6.2",
"react-hook-form": "^7.49.2",
"zod": "^3.22.4",
"@hookform/resolvers": "^3.3.3",
"date-fns": "^3.0.6",
"clsx": "^2.0.0",
"lucide-react": "^0.303.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.0.8",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"tailwindcss": "^3.4.0"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
}
}

60
web/src/App.tsx Normal file
View File

@ -0,0 +1,60 @@
/**
* App Component
* Root component con routing básico
*
* @author Frontend-Agent
* @date 2025-11-20
*/
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
/**
* Componente principal de la aplicación
* TODO: Agregar rutas de los diferentes portales (admin, supervisor, obra)
*/
function App() {
return (
<BrowserRouter>
<div className="app">
<Routes>
{/* Ruta principal */}
<Route path="/" element={<HomePage />} />
{/* Portal Admin */}
<Route path="/admin/*" element={<div>Admin Portal (TODO)</div>} />
{/* Portal Supervisor */}
<Route path="/supervisor/*" element={<div>Supervisor Portal (TODO)</div>} />
{/* Portal Obra */}
<Route path="/obra/*" element={<div>Obra Portal (TODO)</div>} />
{/* 404 */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</div>
</BrowserRouter>
);
}
/**
* Página de inicio temporal
*/
function HomePage() {
return (
<div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
<h1>🏗 Sistema Administración de Obra</h1>
<p>MVP - INFONAVIT</p>
<ul>
<li><a href="/admin">Portal Administrador</a></li>
<li><a href="/supervisor">Portal Supervisor</a></li>
<li><a href="/obra">Portal Obra</a></li>
</ul>
<p style={{ marginTop: '2rem', color: '#666' }}>
Versión: 1.0.0 | Entorno: {import.meta.env.MODE}
</p>
</div>
);
}
export default App;

125
web/src/index.css Normal file
View File

@ -0,0 +1,125 @@
/**
* Global Styles
* Estilos base y reset CSS
*/
/* Reset básico */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* Variables CSS (TODO: mover a theme) */
:root {
--primary-color: #2563eb;
--secondary-color: #64748b;
--success-color: #22c55e;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--text-primary: #1e293b;
--text-secondary: #64748b;
--text-light: #94a3b8;
--bg-primary: #ffffff;
--bg-secondary: #f8fafc;
--bg-tertiary: #f1f5f9;
--border-color: #e2e8f0;
--border-radius: 0.5rem;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
/* Tipografía base */
html {
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.5;
color: var(--text-primary);
background-color: var(--bg-secondary);
}
/* Enlaces */
a {
color: var(--primary-color);
text-decoration: none;
transition: color 0.2s;
}
a:hover {
color: #1d4ed8;
}
/* Buttons (estilos base) */
button {
font-family: inherit;
font-size: inherit;
cursor: pointer;
border: none;
background: none;
transition: all 0.2s;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Inputs (estilos base) */
input,
textarea,
select {
font-family: inherit;
font-size: inherit;
}
/* Scrollbar personalizado (opcional) */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-tertiary);
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-light);
}
/* Utilidades */
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 0 1rem;
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}

18
web/src/main.tsx Normal file
View File

@ -0,0 +1,18 @@
/**
* Main Entry Point
* MVP Sistema Administración de Obra e INFONAVIT
*
* @author Frontend-Agent
* @date 2025-11-20
*/
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);

1
web/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

40
web/tsconfig.json Normal file
View File

@ -0,0 +1,40 @@
{
"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/*"],
"@shared/*": ["./src/shared/*"],
"@components/*": ["./src/shared/components/*"],
"@stores/*": ["./src/shared/stores/*"],
"@services/*": ["./src/shared/services/*"],
"@hooks/*": ["./src/shared/hooks/*"],
"@types/*": ["./src/shared/types/*"],
"@utils/*": ["./src/shared/utils/*"],
"@constants/*": ["./src/shared/constants/*"],
"@apps/*": ["./src/apps/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

11
web/tsconfig.node.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

45
web/vite.config.ts Normal file
View File

@ -0,0 +1,45 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@shared': path.resolve(__dirname, './src/shared'),
'@components': path.resolve(__dirname, './src/shared/components'),
'@stores': path.resolve(__dirname, './src/shared/stores'),
'@services': path.resolve(__dirname, './src/shared/services'),
'@hooks': path.resolve(__dirname, './src/shared/hooks'),
'@types': path.resolve(__dirname, './src/shared/types'),
'@utils': path.resolve(__dirname, './src/shared/utils'),
'@constants': path.resolve(__dirname, './src/shared/constants'),
'@apps': path.resolve(__dirname, './src/apps'),
},
},
server: {
port: 5173,
host: 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'],
'axios-vendor': ['axios'],
},
},
},
},
});