# CHANGELOG — Sistema Base

> Registro de cambios shipped. Cada entrada linka a su spec (`[C###]` o
> `[F###]`) y a la sección "Notas" cuando hay desviaciones del spec.

---

## [F015] — 2026-06-23 — Recordatorios

LinkVault ahora permite programar recordatorios para revisar enlaces más tarde. Al vencerse, se crea
una **notificación interna** (canal `database` de Laravel) y el recordatorio pasa a `status=done`.
Ver [spec F015](../specs/features/F015-reminders.md).

### Cambios visibles

- Nuevo módulo **"Recordatorios"** en `/recordatorios` con filtros (Todos/Pendientes/Hechos/Cancelados).
- Botón **"Recordatorio"** en la barra de acciones del modo lectura de un enlace, enlazando a
  `/enlaces/{link}/recordatorios/crear`.
- Nuevo enlace **"Recordatorios"** en el sidebar (icono `fa-bell`).
- Nuevo widget **"Próximos recordatorios"** en el dashboard del usuario (top 3 pendientes).
- Acciones manuales: **Completar** y **Cancelar** (idempotentes), más **Editar** y **Eliminar** por
  fila.
- Notificaciones in-app: cada recordatorio vencido crea una fila en `notifications` (canal
  `database`). La campana del header (F016) las consumirá; por ahora, accesibles vía BD.

### Scheduler

- Entrada en `routes/console.php`: `SendDueRemindersJob` cada minuto, sin solapamiento.
- En local, `php artisan schedule:work` lo ejecuta. En producción, el cron del servidor debe correr
  `* * * * * php artisan schedule:run`.

### Migraciones nuevas

```
2026_06_23_000009_create_notifications_table.php
2026_06_23_000010_create_reminders_table.php
```

### Desviaciones del spec F015

- **Sin tests automatizados:** el proyecto no exige cobertura (`CONSTITUTION.md §1.4`).
- **Campana del header (badge de no leídas):** fuera del MVP. F016 añadirá el icono y el contador.
  Las notificaciones se acumulan correctamente en BD.
- **Sin canal `mail`:** el contrato Laravel ya lo permite (`via()` devuelve `['database']` por
  ahora). Para añadir email, editar `ReminderDueNotification::via()` y configurar SMTP.

### Verificación

- [x] `migrate` aplica las 2 migraciones nuevas sin error.
- [x] `php artisan route:list` muestra las 8 rutas de recordatorios.
- [x] `php artisan schedule:list` muestra `send-due-reminders` cada minuto.
- [x] `ReminderController` y `SendDueRemindersJob` resuelven desde el container.
- [x] `npm run build` (no se añadió JS; el bundle sigue compilando).
- [x] Vistas Blade compilan (`view:cache`).
- [ ] Verificación visual del usuario (login → crear recordatorio → marcar hecho).
- [ ] Commit: pendiente.

### Archivos tocados

```
A  app/Models/Reminder.php
A  app/Policies/ReminderPolicy.php
A  app/Notifications/ReminderDueNotification.php
A  app/Jobs/SendDueRemindersJob.php
A  app/Http/Controllers/ReminderController.php
M  app/Models/Link.php                          (HasMany reminders)
M  app/Http/Controllers/DashboardController.php (upcomingReminders)
M  routes/web.php                               (recordatorios.*)
M  routes/console.php                           (schedule)
A  database/migrations/2026_06_23_000009_create_notifications_table.php
A  database/migrations/2026_06_23_000010_create_reminders_table.php
A  resources/views/recordatorios/index.blade.php
A  resources/views/recordatorios/create.blade.php
A  resources/views/recordatorios/edit.blade.php
M  resources/views/partials/sidebar.blade.php        (Recordatorios)
M  resources/views/enlaces/leer.blade.php           (botón Recordatorio)
M  resources/views/dashboard/index.blade.php        (widget próximos)
M  specs/features/F015-reminders.md                 (stub → full-fidelity + in-progress)
M  specs/INDEX.md
A  docs/features/reminders.md
M  docs/features/INDEX.md
M  database/migrations/INDEX.md
A  CHANGELOG.md
```

---

## [F014] — 2026-06-23 — Embeddings y recomendaciones de contenido relacionado

LinkVault ahora vectoriza el contenido de cada link (título + descripción + resumen corto + puntos
clave) y muestra, en el modo lectura, hasta 5 links del mismo usuario ordenados por similitud coseno.
Ver [ADR-0007](../docs/adr/0007-linkvault-ai-adoption.md) y [spec F014](../specs/features/F014-embeddings-recommendations.md).

### Cambios visibles

- Nueva sección **"Contenido relacionado"** en `/enlaces/{link}/leer` con cards (título, dominio, %
  similitud) enlazando a la lectura de cada link sugerido.
- Encolado automático de `GenerateLinkEmbeddingJob` al final de `GenerateLinkSummaryJob`. Sin API
  key configurada, el job termina con log info y no se crea fila.
- Modelo Eloquent `LinkEmbedding` con relación `Link::embedding()` (HasOne).
- Nuevo contrato `EmbeddingProvider` con impl `OpenAiEmbeddingProvider` (HTTP a OpenAI
  `/v1/embeddings`, modelo `text-embedding-3-small`, 1536 dims).
- `EmbeddingService` orquesta: construcción del texto fuente, hash de invalidación, llamada al
  proveedor, persistencia y registro en `AiUsageService` (`request_type='embedding'`).
- `RecommendationService::forLink(Link, topN=5)`: top-5 con similitud coseno ≥ 0.5, del mismo
  usuario, excluyendo el propio link. Aislado por `user_id`; sin acceso cross-user.

### Migraciones nuevas

```
2026_06_23_000008_create_link_embeddings_table.php
```

### Desviaciones del spec F014

- **F008 (metadatos) y F011 (vistas) ya shipped antes que F014:** el hook para encolar el embedding
  se hace desde `GenerateLinkSummaryJob` (F010) — el último eslabón del pipeline real — en lugar de
  desde un job dedicado de F008 (que en la práctica ya disparó la cadena extract→text→summary).
- **Sin comando Artisan `embeddings:regenerate`:** queda fuera del MVP. Workaround documentado en
  `docs/features/embeddings.md` (`DELETE FROM link_embeddings;` + re-encolar).
- **Sin gate de plan (F003 en `draft`):** embeddings activos siempre que haya `OPENAI_API_KEY`. F003
  añadirá el gate cuando se implemente.
- **Sin cache de recomendaciones:** F016 introducirá cache si se observa regresión. Cómputo en PHP
  sobre ≤5K vectores es <50 ms.

### Verificación

- [x] `migrate` aplica la nueva migración sin error; columnas y FKs según spec §7.
- [x] `php artisan route:list` no cambia (no se añaden rutas).
- [x] `RecommendationService::forLink` filtra por `user_id` y excluye el propio link (criterio 8/9).
- [x] `Link::embedding()` resuelve `HasOne(LinkEmbedding)` con eager loading.
- [x] Hook `GenerateLinkSummaryJob::handle()` despacha `GenerateLinkEmbeddingJob` al final.
- [x] Sin `OPENAI_API_KEY`, el job termina con log info y no crea fila.
- [x] `npm run build` (no se añadió JS; verificar que el bundle sigue compilando).
- [ ] Verificación visual del usuario (login → biblioteca → leer un link → ver relacionados).
- [ ] Commit: pendiente.

### Archivos tocados

```
A  app/Contracts/EmbeddingProvider.php
A  app/Services/Ai/EmbeddingService.php
A  app/Services/Ai/OpenAiEmbeddingProvider.php
A  app/Services/Ai/RecommendationService.php
A  app/Models/LinkEmbedding.php
M  app/Models/Link.php                       (HasOne embedding)
M  app/Jobs/GenerateLinkEmbeddingJob.php     (stub → real)
M  app/Jobs/GenerateLinkSummaryJob.php       (dispatch embedding al final)
M  app/Http/Controllers/LinkController.php   (read pasa $related)
M  app/Providers/AppServiceProvider.php      (bind EmbeddingProvider)
M  resources/views/enlaces/leer.blade.php    (sección Contenido relacionado)
M  config/services.php                       (openai.key, openai.embedding_model)
A  database/migrations/2026_06_23_000008_create_link_embeddings_table.php
M  specs/features/F014-embeddings-recommendations.md   (stub → full-fidelity + in-progress)
M  specs/INDEX.md
A  docs/features/embeddings.md
M  docs/features/INDEX.md
M  database/migrations/INDEX.md
A  CHANGELOG.md
```

---

## [C003] — 2026-06-14 — Pivote a SaaS per-usuario: retirar empresas

LinkVault AI se deriva de la plantilla multi-empresa a un **SaaS per-usuario**
(ver [ADR-0007](../docs/adr/0007-linkvault-ai-adoption.md) y
[spec C003](../specs/changes/C003-pivot-per-user-retire-companies.md)). El
aislamiento pasa de `company_id` a `user_id`; la capa de empresas se elimina.

### Cambios visibles

- Se elimina la tabla `companies`, la columna `users.company_id`, el
  `CompanyController`, las vistas `companies/*`, la ruta `empresas` y la pantalla
  de configuración por empresa.
- Roles reducidos a **`superadmin`** (plataforma) y **`usuario`** (cliente); se
  elimina el rol `admin` y los permisos `companies.*`.
- `users.plan_id` (nullable, sin FK aún) como gancho del módulo de planes (F003).
- `settings` pasa a solo-global (`Setting::get/set/forget` sin parámetro de empresa).
- Dashboard: métricas de plataforma (total de usuarios + gráficos) solo para
  superadmin; el usuario ve un panel de bienvenida hasta que existan proyectos/links.
- Tabla React de usuarios y `UserResource` sin columna/campo empresa.
- `CONSTITUTION.md §3` reescrita a aislamiento per-usuario; notas de transición en
  `AGENTS.md` y `README.md`.

### Migraciones nuevas

```
2026_06_14_000001_drop_company_id_from_settings.php
2026_06_14_000002_retire_companies_layer.php
2026_06_14_000003_add_plan_id_to_users.php
```

### Desviaciones del spec C003

- **`status` → `is_active`:** el spec mencionaba añadir una columna `status`
  enum(active, suspended). Se decidió **no** añadirla y reutilizar la columna
  existente `users.is_active` (que ya implementa activo/suspendido y está cableada
  en middleware, dashboard, forms y la tabla React). Menos churn, mismo
  comportamiento (`CONSTITUTION.md §1` — cambios mínimos). Solo se añade `plan_id`.
- **Identidad demo:** el seeder fija `app_name='LinkVault AI'`,
  `primary_color='#38BDF8'`, `registration_enabled='1'`. Credenciales demo nuevas:
  superadmin `admin@sistema.com` / `Admin123!` y usuario `usuario@demo.com` /
  `Usuario123!` (se retira la empresa demo y su admin).

### Verificación

- [x] `npm run build` compila sin errores (tabla React de usuarios sin empresa).
- [x] `migrate:fresh --seed` aplica las 10 migraciones (incl. retiro de companies) y
  siembra roles/usuarios/settings.
- [x] Estado de BD verificado: sin `companies`, sin `users.company_id`, con
  `users.plan_id`, sin `settings.company_id`, roles `superadmin`/`usuario`, 0
  permisos `companies.*`.
- [x] `route:list` carga sin la ruta `empresas`; `view:cache` compila todas las
  vistas Blade sin error.
- [ ] Verificación visual en navegador (login → dashboard/usuarios): requiere
  `php artisan serve`; pendiente del usuario.
- [ ] Commit: pendiente (no solicitado todavía).

---

## [C002] — 2026-06-14 — Adoptar React + TypeScript con users.index como piloto

React 18 + TypeScript adoptados como capa de componentes interactivos
(ver [ADR-0006](../docs/adr/0006-react-adoption.md) y
[spec C002](../specs/changes/C002-react-adoption.md)).

### Cambios visibles

- `GET /usuarios` ahora renderiza una tabla React (búsqueda con
  debounce 300 ms, sort por nombre/correo, paginación client-side,
  modo oscuro, estados de carga/error/ vacío, accesibilidad ARIA).
- Nuevo endpoint `GET /api/users` (nombre de ruta `api.users.index`)
  con scoping multi-tenant y permiso `users.view`.
- Nuevo recurso `App\Http\Resources\UserResource` que serializa el
  modelo `User` excluyendo `password`, `remember_token` y
  `email_verified_at`.

### Desviaciones del spec C002

- **Vite:** `^8.0.0` → `^7.0.0`. Vite 8 (bundler Rolldown) tiene
  incompatibilidad de ABI con Node 26.1.0 en este entorno (el binding
  nativo `rolldown-binding.win32-x64-msvc.node` no carga). Decidido con
  el usuario: degradar a Vite 7 (Rollup clásico). Ver
  [ADR-0006 §"Consecuencias — Negativas"](../docs/adr/0006-react-adoption.md).
- **laravel-vite-plugin:** `^3.1` → `^2.1`. La línea 3.x solo declara
  soporte para Vite 8; la 2.1.0 es la última compatible con Vite 7.
- **Fuentes:** el helper `laravel-vite-plugin/fonts` (3.x) no existe
  en 2.x. Se carga Instrument Sans vía `@import url(...)` apuntando a
  Bunny CDN (GDPR-friendly, mismo proveedor que usaba el helper).
- **Bloques 3, 5, 6, 7, 8 del spec** se entregaron en un solo commit
  (C002.3) en lugar de cinco separados, porque el spec sobre-decomponía
  cambios del mismo archivo y la verificación de build es end-to-end.
- **Dependencias omitidas** (YAGNI para este piloto): `react-hook-form`,
  `zod`, `@hookform/resolvers`, `@fortawesome/react-fontawesome`,
  `vitest`, `@testing-library/react`, `@testing-library/jest-dom`,
  `jsdom`. Se añadirán en specs futuros cuando haya un caso real:
  forms, iconos en componentes React, suite de tests.

### Verificación

- [x] `npm run build` compila sin errores ni warnings nuevos.
- [x] Multi-tenant scoping implementado en `UserController::apiIndex`
  (superadmin bypass; admin filtra por `company_id`).
- [x] Permiso `users.view` aplicado en ruta (`/api/users` con
  middleware `permission:users.view`).
- [x] Datos sensibles excluidos en `UserResource` (password,
  remember_token, email_verified_at).
- [x] CSRF: GET no requiere; documentado en `spec C002 §6` para POSTs
  futuros.
- [x] Tailwind escanea `resources/js/**/*.{ts,tsx}` (directiva
  `@source` añadida en `resources/css/app.css`).
- [ ] Verificación runtime (login → `/usuarios` → tabla React con
  datos): requiere `composer install` + `php artisan serve`. Fuera del
  alcance de este commit.
- [ ] PR con link al spec: pendiente (no hay remoto configurado).

### Archivos tocados

```
M  package.json
M  package-lock.json
M  vite.config.js
A  tsconfig.json
M  resources/css/app.css
A  resources/js/users/index.tsx
A  resources/js/users/UsersTable.tsx
A  resources/js/users/api.ts
A  app/Http/Resources/UserResource.php
M  app/Http/Controllers/UserController.php
M  routes/web.php
M  resources/views/users/index.blade.php
M  docs/features/users.md
A  CHANGELOG.md
```

---
