# Guía práctica — Sistema Base

> **Esta es la guía práctica** del proyecto: mapa de archivos, modelo de datos, rutas, troubleshooting, referencia rápida.
> Las **reglas innegociables** viven en [`CONSTITUTION.md`](CONSTITUTION.md). Si hay conflicto, gana la constitución.

## Antes de actuar

1. Lee [`CONSTITUTION.md`](CONSTITUTION.md) — son las reglas que no se rompen.
2. Lee [`docs/workflow.md`](docs/workflow.md) — las 5 fases del SDD, los gates, las excepciones.
3. Busca un spec activo en [`specs/`](specs/) relacionado con tu tarea. Si no existe, crea uno (plantillas en [`docs/spec-template-*.md`](docs/)).
4. Mantén el scope mínimo. No refactorices lo no pedido.
5. Si encuentras algo incorrecto o desactualizado aquí, actualízalo en el mismo cambio.

---

## 1. Identidad del proyecto

- Plantilla base para derivar sistemas. **En transición a SaaS per-usuario** (LinkVault AI): ver
  [ADR-0007](docs/adr/0007-linkvault-ai-adoption.md) y
  [C003](specs/changes/C003-pivot-per-user-retire-companies.md). `CONSTITUTION.md §3` ya rige el modelo
  per-usuario (`user_id`); **este documento aún describe el código multi-empresa** (`company_id`) hasta que
  C003 se implemente. Donde haya conflicto, gana la constitución.
- Idioma de TODA la interfaz, mensajes, validaciones y comentarios: **español**.
- Las rutas web usan slugs en español (`/usuarios`, `/empresas`, `/configuracion`), pero los nombres internos de rutas, tablas, modelos y permisos van en **inglés** (`users.index`, tabla `users`, permiso `users.view`).

---

## 2. Stack obligatorio

| Capa | Tecnología | Regla |
|---|---|---|
| Backend | PHP 8.2+ / Laravel 13 | No degradar versiones |
| Plantillas | Blade | Blade renderiza páginas y vistas; React se permite como librería de componentes embebidos en vistas (no como reemplazo de páginas). Vue/Livewire/Svelte requieren ADR propio. Ver [ADR-0006](docs/adr/0006-react-adoption.md). |
| CSS | Tailwind CSS 4 vía Vite | No CSS suelto ni Bootstrap; nada de estilos inline salvo el color del tema |
| Componentes interactivos | React 18 + TypeScript | Tablas con estado, formularios complejos, widgets con fetch. Montados como "islands" dentro de vistas Blade. |
| Interacciones ligeras | Alpine.js | Dropdowns, modals, toggles. Convive con React en el bundle global; usar uno u otro según complejidad del estado. |
| Gráficos | Chart.js (global `window.Chart`) | No otras librerías de gráficos |
| Iconos | FontAwesome Free (`fa-solid`, etc.) | No SVGs sueltos ni otras librerías de iconos |
| BD | MySQL/MariaDB, charset `utf8mb4_unicode_ci` | No SQLite/Postgres en este proyecto |
| Permisos | spatie/laravel-permission | No sistemas de roles caseros |

Comandos: `npm run dev` en desarrollo, `npm run build` antes de entregar, `php artisan serve` para el servidor local. Composer está en `~/.local/bin/composer`.

---

## 3. Modelo de datos

> ⚠ **En transición (ADR-0007 / C003).** Lo de abajo refleja el código **actual** (multi-empresa). Tras
> C003: `companies` se elimina, `users.company_id` desaparece (se añaden `plan_id` y `status`), y el dominio
> de LinkVault (projects, folders, links, categories, tags, …) se aísla por `user_id`. Modelo objetivo en
> [F002 §6](specs/features/F002-linkvault-roadmap.md).

### Tablas principales

| Tabla | Propósito | `company_id` | `SoftDeletes` |
|---|---|---|---|
| `users` | Usuarios del sistema | Sí (nullable para superadmin) | No |
| `companies` | Empresas (tenants) | No (es la fuente) | Sí |
| `settings` | Configuración clave-valor | Sí (nullable para global) | No |
| `roles` / `permissions` / pivotes | Spatie permission | No | No |
| `cache` / `jobs` / `sessions` | Laravel core | No | No |

### Relaciones

```
Company 1──N User       (company_id en users)
Company 1──N Setting    (company_id en settings, nullable)
User    N──N Role       (model_has_roles, Spatie)
Role    N──N Permission (role_has_permissions, Spatie)
```

### Modelos Eloquent

| Modelo | Archivo | `$fillable` | Casts | Relaciones |
|---|---|---|---|---|
| `User` | `app/Models/User.php` | name, email, password, company_id, is_active, avatar_path | email_verified_at: datetime, last_login_at: datetime, is_active: boolean, password: hashed | `company(): BelongsTo` |
| `Company` | `app/Models/Company.php` | name, slug, email, phone, address, logo_path, primary_color, is_active | is_active: boolean | `users(): HasMany`, `settings(): HasMany` |
| `Setting` | `app/Models/Setting.php` | company_id, key, value | — | `company(): BelongsTo` |

### Métodos clave de modelo

- `User::isSuperAdmin(): bool` — verifica si tiene rol `superadmin`.
- `User::initials(): string` — iniciales para avatar (2 letras).
- `Setting::get(key, default, companyId): mixed` — lectura con fallback empresa→global.
- `Setting::set(key, value, companyId): void` — escritura con invalidación de caché.
- `Setting::forget(key, companyId): void` — borrado con invalidación de caché.

---

## 4. Rutas

Archivo: `routes/web.php`

### Públicas (middleware `guest`)

| Método | URI | Nombre | Controlador |
|---|---|---|---|
| GET | `/login` | `login` | `AuthController@create` |
| POST | `/login` | `login.store` | `AuthController@store` |
| GET | `/registro` | `register` | `RegisterController@create` |
| POST | `/registro` | `register.store` | `RegisterController@store` |

### Autenticadas (middleware `auth` + `active`)

| Método | URI | Nombre | Controlador | Middleware extra |
|---|---|---|---|---|
| POST | `/logout` | `logout` | `AuthController@destroy` | — |
| GET | `/dashboard` | `dashboard` | `DashboardController@index` | `permission:dashboard.view` |
| GET | `/perfil` | `profile.edit` | `ProfileController@edit` | — |
| PATCH | `/perfil` | `profile.update` | `ProfileController@update` | — |
| PUT | `/perfil/password` | `profile.password` | `ProfileController@updatePassword` | — |
| resource | `/empresas` | `companies.*` | `CompanyController` | `role:superadmin` |
| resource | `/usuarios` | `users.*` | `UserController` | `permission:users.view` |
| resource | `/roles` | `roles.*` | `RoleController` | `role:superadmin` |
| GET | `/configuracion` | `settings.edit` | `SettingController@edit` | `permission:settings.view` |
| PUT | `/configuracion` | `settings.update` | `SettingController@update` | `permission:settings.edit` |

---

## 5. Controladores

| Controlador | Archivo | Líneas aprox. | Descripción |
|---|---|---|---|
| `AuthController` | `app/Http/Controllers/Auth/AuthController.php` | 51 | Login/logout. Verifica estado activo tras login. |
| `RegisterController` | `app/Http/Controllers/Auth/RegisterController.php` | 53 | Registro público (gateado por `registration_enabled`). |
| `DashboardController` | `app/Http/Controllers/DashboardController.php` | 46 | Stats + gráfico de registros mensuales. Scoping manual por empresa. |
| `CompanyController` | `app/Http/Controllers/CompanyController.php` | 97 | CRUD empresas. Helpers: `validateCompany`, `uniqueSlug`. |
| `UserController` | `app/Http/Controllers/UserController.php` | 172 | CRUD usuarios. Helpers: `authorizeAccess`, `validateUser`, `resolveCompanyId`, `assignableRoles`, `formData`. |
| `RoleController` | `app/Http/Controllers/RoleController.php` | 98 | CRUD roles. Protege roles del sistema. Helper: `groupedPermissions`. |
| `SettingController` | `app/Http/Controllers/SettingController.php` | 66 | Configuración dual: superadmin ve global, otros ven color de empresa. |
| `ProfileController` | `app/Http/Controllers/ProfileController.php` | 43 | Perfil propio: editar datos + cambiar contraseña. |

### Middleware

| Middleware | Archivo | Descripción |
|---|---|---|
| `EnsureUserIsActive` | `app/Http/Middleware/EnsureUserIsActive.php` | Verifica usuario y empresa activos en cada request autenticado. Cierra sesión si no. |

### Requests

| FormRequest | Archivo | Descripción |
|---|---|---|
| `LoginRequest` | `app/Http/Requests/Auth/LoginRequest.php` | Validación de login con rate limiting (5 intentos/min). |

---

## 6. Permisos (Spatie)

### Permisos registrados

| Permiso | Asignado a |
|---|---|
| `dashboard.view` | admin, usuario |
| `companies.view/create/edit/delete` | (solo superadmin vía Gate::before) |
| `users.view/create/edit/delete` | admin |
| `roles.view/create/edit/delete` | (solo superadmin vía Gate::before) |
| `settings.view/edit` | admin |

### Roles del sistema (protegidos)

| Rol | Descripción |
|---|---|
| `superadmin` | Acceso total vía `Gate::before`. No asignar permisos manualmente. |
| `admin` | Gestiona usuarios y configuración de su empresa. |
| `usuario` | Solo dashboard. |

Seeder: `database/seeders/RolesAndPermissionsSeeder.php` (idempotente con `findOrCreate`).

---

## 7. Vistas y componentes

### Layouts

| Layout | Archivo | Uso |
|---|---|---|
| `layouts.app` | `resources/views/layouts/app.blade.php` | Panel autenticado |
| `layouts.guest` | `resources/views/layouts/guest.blade.php` | Login, registro |

### Componentes Blade (`resources/views/components/`)

`<x-card>`, `<x-input>`, `<x-select>`, `<x-button variant="primary|secondary|danger">`, `<x-label>`, `<x-error for="campo">`, `<x-flash>`

### Parciales

| Parcial | Archivo | Descripción |
|---|---|---|
| Sidebar | `resources/views/partials/sidebar.blade.php` | Navegación lateral con iconos FA y `@can` |
| Header | `resources/views/partials/header.blade.php` | Barra superior con toggle dark mode |

### Tema configurable

- Color primario: variable CSS `--app-primary`, inyectada por `App\View\Composers\ThemeComposer`.
- Fuentes: `settings.app_name` (global) + `companies.primary_color` (override por empresa).
- Utilidades Tailwind: `bg-primary`, `text-primary`, `bg-primary-dark` (hover), `bg-primary-light` (fondos suaves).
- Chart.js: leer con `getComputedStyle(document.documentElement).getPropertyValue('--app-primary')`.

---

## 8. Convención de commits

Formato: `[F###|C###|H###]: acción corta`

Ejemplos:
- `C001: extraer CompanyScoped trait`
- `F002: módulo de facturación`
- `H001: corregir validación de email duplicado`

Documentado también en [`CONSTITUTION.md §6.6`](CONSTITUTION.md) y [`§8`](CONSTITUTION.md).

---

## 9. Configuración (tabla `settings`)

| Clave | Descripción | Default |
|---|---|---|
| `app_name` | Nombre del sistema (sidebar, títulos) | `Sistema Base` |
| `primary_color` | Color primario global | `#6366f1` |
| `registration_enabled` | `1` habilita registro público | `1` |
| `default_role` | Rol asignado al registrarse | `usuario` |

API: `Setting::get($key, $default, $companyId)` / `Setting::set(...)` / `Setting::forget(...)`.

---

## 10. Migraciones

| # | Archivo | Propósito |
|---|---|---|
| 1 | `0001_01_01_000000_create_users_table.php` | Tabla users + password resets |
| 2 | `0001_01_01_000001_create_cache_table.php` | Caché de Laravel |
| 3 | `0001_01_01_000002_create_jobs_table.php` | Cola de jobs |
| 4 | `2026_06_10_225544_create_permission_tables.php` | Tablas de Spatie permission |
| 5 | `2026_06_10_225728_create_companies_table.php` | Tabla companies (multi-tenant) |
| 6 | `2026_06_10_225729_add_company_fields_to_users_table.php` | Añadir company_id y campos a users |
| 7 | `2026_06_10_225730_create_settings_table.php` | Tabla settings (clave-valor con company_id) |

Índice detallado: [`database/migrations/INDEX.md`](database/migrations/INDEX.md)

---

## 11. Troubleshooting

| Problema | Causa probable | Solución |
|---|---|---|
| `npm run build` falla | Node modules desactualizados | `rm -rf node_modules && npm install` |
| 403 en ruta protegida | Permiso no asignado al rol | Ejecutar `php artisan db:seed --class=RolesAndPermissionsSeeder` |
| Caché de permisos stale | Permisos cambiados sin limpiar | `php artisan permission:cache-reset` |
| Vista imprime componente como texto | Directiva Blade dentro de etiqueta de componente | Usar `:required="$condicion"` en vez de `@if` |
| Migración falla por FK | Orden de timestamps incorrecto | Verificar que la migración padre tenga timestamp anterior |

---

## 12. Credenciales de desarrollo

| Rol | Correo | Contraseña |
|---|---|---|
| Superadmin | `admin@sistema.com` | `Admin123!` |
| Admin Empresa Demo | `admin@empresademo.com` | `Admin123!` |
| Usuario Empresa Demo | `usuario@empresademo.com` | `Usuario123!` |

BD local: `elink` en MariaDB de XAMPP (usuario `root` sin contraseña, socket `/Applications/XAMPP/xamppfiles/var/mysql/mysql.sock`).

---

## 13. Mapa de features

Catálogo completo de features implementadas: [`docs/features/INDEX.md`](docs/features/INDEX.md)

| Feature | Doc | Estado |
|---|---|---|
| Auth (login/registro) | [`docs/features/auth.md`](docs/features/auth.md) | stub |
| Dashboard | [`docs/features/dashboard.md`](docs/features/dashboard.md) | stub |
| Empresas | [`docs/features/companies.md`](docs/features/companies.md) | stub |
| Usuarios | [`docs/features/users.md`](docs/features/users.md) | completo |
| Roles/Permisos | [`docs/features/roles.md`](docs/features/roles.md) | stub |
| Configuración | [`docs/features/settings.md`](docs/features/settings.md) | stub |
| Perfil | [`docs/features/profile.md`](docs/features/profile.md) | stub |

---

*Última actualización: 2026-06-14 — ADR-0007 aceptado; notas de transición a per-usuario (LinkVault AI / C003). El mapa de código se reescribe cuando C003 se implemente.*
