ERP interno para PyMEs single-tenant: inventario, ventas y clientes, con UI dark estilo Linear/Stripe.
- Backend: Laravel 13 · PHP 8.3 · MySQL 8
- Panel back-office: Filament v4 (tema dark custom zinc + violet)
- POS y vistas hero: Inertia + Vue 3 + TypeScript + Tailwind v4 + shadcn-vue
- PDF: barryvdh/laravel-dompdf
- Permisos: spatie/laravel-permission
- Tests: Pest 4
- PHP 8.3 + extensiones PDO MySQL
- Composer 2.x
- Node 20+ (con npm)
- MySQL 8 corriendo localmente
- Sólo macOS/Linux probados (Windows debería funcionar con WSL2)
git clone <repo> NexaERP && cd NexaERP
# Dependencias
composer install
npm install
# Configuración
cp .env.example .env
php artisan key:generate
# Crear BD y usuario MySQL
mysql -uroot -p <<SQL
CREATE DATABASE nexaerp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'nexaerp_user'@'localhost' IDENTIFIED BY 'TU_PASSWORD';
GRANT ALL PRIVILEGES ON nexaerp.* TO 'nexaerp_user'@'localhost';
FLUSH PRIVILEGES;
SQL
# Editar .env con DB_USERNAME=nexaerp_user y DB_PASSWORD=...
# Migrar + sembrar datos demo
php artisan migrate:fresh --seed# Terminal 1: servidor Laravel
php artisan serve --host=127.0.0.1 --port=8080
# Terminal 2: Vite (HMR)
npm run devAbre http://127.0.0.1:8080 (no uses localhost si tienes Docker corriendo en :8000).
| Rol | Aterriza en | |
|---|---|---|
| admin@nexaerp.test | Admin | /dashboard |
| vendedor@nexaerp.test | Vendedor | /dashboard |
| almacen@nexaerp.test | Almacén | /admin/products |
# Tests
./vendor/bin/pest
# Backup manual
php artisan db:backup
# (los backups quedan en storage/app/backups, retención 14 días)
# Reset de datos demo
php artisan migrate:fresh --seed
# Build de assets para producción
npm run build
# Linter PHP
./vendor/bin/pintapp/
├── Enums/ # StockMovementType, SaleStatus, PaymentMethod, DocumentType
├── Filament/ # Back-office (panel /admin)
│ ├── Pages/ # StockAdjustment, Settings
│ ├── Resources/ # Category, Product, Customer, StockMovement
│ └── Widgets/ # LowStockWidget
├── Http/Controllers/
│ ├── App/ # Controladores Inertia (Dashboard, Sale, Pos, búsquedas API)
│ └── Auth/ # LoginController
├── Models/ # Eloquent
├── Services/ # Lógica de negocio (StockService, SaleService, InvoicePdfService)
└── Console/Commands/ # BackupDatabaseCommand
resources/js/
├── Components/
│ ├── data/ # EmptyState, KpiCard, SalesChart
│ ├── sales/ # PaymentDialog, CancelSaleDialog, RecordPaymentDialog
│ └── ui/ # Button, Input, Dialog, Toast, Skeleton, Badge, Card...
├── Layouts/ # AppLayout.vue
├── Pages/ # Vistas Inertia (Dashboard, Sales/*, Auth/Login)
├── composables/ # useSaleCart, useShortcuts
└── lib/ # cn, formatters
stock_movementses la fuente de la verdad.products.stockes un cache denormalizado actualizado víaStockService.- Confirmar una venta crea un
stock_movement type=outpor cadasale_item, conreferencepolimórfica a la Sale. - Anular una venta revierte el stock (tipo
in) con auditoría. - Toda operación crítica (confirmar, anular, registrar pago, ajustar stock) corre en
DB::transactionconlockForUpdatesobreproducts.stock. - Números de venta:
V-YYYY-NNNNNsecuencial por año, generado en transacción.
| Recurso / Acción | Admin | Vendedor | Almacén |
|---|---|---|---|
| Usuarios CRUD | ✔ | ✘ | ✘ |
| Clientes CRUD | ✔ | ✔ | ✘ |
| Productos CRUD | ✔ | ✘ | ✔ |
| Ajuste de stock | ✔ | ✘ | ✔ |
| Crear / cobrar venta | ✔ | ✔ | ✘ |
| Anular venta | ✔ | ✘ | ✘ |
| Ajustes empresa | ✔ | ✘ | ✘ |
Ver DEPLOY.md para el procedimiento detallado.
Programar el scheduler de Laravel en producción:
* * * * * cd /var/www/nexaerp && php artisan schedule:run >> /dev/null 2>&1Esto ejecuta db:backup diario a las 02:00. Los archivos quedan en storage/app/backups/ (gzipped) con retención de 14 días.
Issues internos del proyecto.