Commit Graph
113 Commits
Author SHA1 Message Date
javierandClaude Opus 4.7 51c07ede38 feat(templates): tabla Rappasoft con filtros para el catálogo global
- Nueva InspectionTemplatesTable (DataTableComponent): columnas Plantilla,
  Descripción, Campos, Proyectos (uso) y Acciones; filtros en cabecera por nombre,
  descripción y uso (en uso / sin uso). Gateada por manage templates.
- GlobalTemplateManager: el listado HTML se sustituye por la tabla embebida.
  Acciones Editar/Borrar viajan por eventos (template-edit/template-delete);
  el manager emite templates-changed al guardar/borrar/importar para que la tabla
  se refresque (#[On('templates-changed')]).

Tests: GlobalTemplatesTest amplía con tabla (render + permiso, evento edit). Suite 91 passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
base-trabajo-2026-06-18
2026-06-25 18:24:59 +02:00
javierandClaude Opus 4.7 f8a68312a3 refactor(templates): plantillas globales + asignación por proyecto (pivot)
- Migración: tabla pivot inspection_template_project; drop de phase_id (asociación
  a fase descartada); project_id se mantiene en inspection_templates por compat.
  Migra automáticamente las plantillas existentes (project_id → pivot).
- Modelos: Project::inspectionTemplates() ↔ InspectionTemplate::projects() (BTM);
  retirada la relación phase() de InspectionTemplate.
- GlobalTemplateManager (nuevo, ruta /inspection-templates, permiso manage templates):
  catálogo único global, CRUD + import CSV; sin asociación a fase ni a proyecto.
- ProjectTemplatesPicker (nuevo, ruta projects.templates, permiso edit projects):
  lista global con checkbox para asignar/desasignar plantillas al proyecto.
- ProjectMap: el selector de plantillas del mapa lee SOLO las asignadas al proyecto
  vía pivot. API móvil (bundle + /templates) adaptado al pivot.
- Eliminado el viejo TemplateManager por-proyecto, su vista wrapper y la tabla
  ImportTemplatesTable (ya no aplica: todas son globales).
- Acceso "Plantillas" añadido al menú principal (gate manage templates).

Tests: GlobalTemplatesTest (4). Suite 89 passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 17:26:12 +02:00
javierandClaude Opus 4.8 7256c87182 feat(templates): importar de otro proyecto con tabla Rappasoft + selección múltiple
Sustituye el modal (proyecto + checkboxes) por una tabla Rappasoft:
- ImportTemplatesTable lista todas las plantillas de proyectos accesibles (excluye el
  destino), con filtros en cabecera (nombre, proyecto) y checkbox de selección (bulk).
- Acción masiva "Importar seleccionadas" copia al proyecto destino (dedupe por nombre)
  y avisa a TemplateManager por evento (templates-imported) para refrescar/cerrar.
- TemplateManager: eliminada la lógica vieja (importProjectId/importableTemplates/
  selectedImportTemplateIds/importFromProject); el modal embebe la tabla.

Tests: TemplateImportTest adaptado (selección+import, excluye destino). Suite 92 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:00:35 +02:00
javierandClaude Opus 4.8 1697c16136 feat(inspections): formulario y visor por grupos + comentarios + fotos
Parte 2 del bloque de plantillas/inspecciones:
- Formulario de inspección (mapa): campos agrupados por sección, muestra la
  "pregunta" del campo (con la etiqueta como subtexto) y la ayuda; al final,
  apartado de Comentarios (notes) y subida de Fotos (media en la inspección).
- ProjectMap: WithFileUploads + inspectionPhotos; saveInspection adjunta las fotos
  como media; Inspection gana relación media().
- Visor de inspección: datos agrupados por sección con pregunta/valor, comentarios
  y galería de fotos.

Tests: InspectionFormTest (registra inspección con comentarios + foto). Suite 91 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:51:42 +02:00
javierandClaude Opus 4.8 44f125293b feat(templates): grupos de campos, pregunta/comentarios por campo y plantillas globales
Builder de plantillas (parte 1 del bloque):
- Cada campo admite "Grupo / sección" (título), "Pregunta / revisar" (texto corto,
  tras la etiqueta) y "Comentarios / ayuda" (texto largo, al final).
- Plantillas híbridas: toggle "Global" → project_id null; el listado de un proyecto
  muestra sus plantillas + las globales (badge "Global").

Tests: TemplateCreationTest (grupo/pregunta/ayuda persisten; global sin proyecto y
visible en otros proyectos). Suite 90 passing.

Pendiente (parte 2): adaptar el formulario de inspección (grupos, comentarios, fotos)
y el visor de inspección.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:45:21 +02:00
javierandClaude Opus 4.8 a52be91dd4 feat(api): sincronizar tipo de elemento y activo/inactivo en la app móvil
- bundle: mapFeature incluye feature_type_id e is_active; nuevo array feature_types
  (catálogo) para que el móvil resuelva nombres/colores.
- /sync feature.update acepta is_active y feature_type_id (last-write-wins).
- openapi.yaml: bundle documenta feature_types.

Tests: MobileApiTest cubre feature.update con los nuevos campos + bundle. Suite 88 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:19:33 +02:00
javierandClaude Opus 4.8 3ee70cf48d feat(features): edición de elementos + catálogo de tipos de elemento
- Migración: tabla feature_types + columnas feature_type_id (FK) e is_active en features.
  Modelo FeatureType; Feature gana featureType() + casts is_active.
- Catálogo de tipos (FeatureTypeManager, ruta /feature-types): CRUD con modal
  (nombre, descripción, color), contador de usos; gateado por edit layers.
- Editor de elementos del proyecto (FeatureManager, ruta projects.features):
  tabla Rappasoft ProjectFeaturesTable (filtros nombre/capa/tipo en cabecera),
  toggle activo/inactivo y borrado individual; modal para editar nombre/tipo/activo.
- Acceso: botón "Elementos" en el dashboard del proyecto (edit layers) y enlace a
  "Tipos de elemento" desde el editor.

Tests: FeatureManagementTest (6). Suite 87 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:12:47 +02:00
javierandClaude Opus 4.8 1decb19bba feat(templates): exponer importación desde otro proyecto y desde CSV/Excel
El TemplateManager ya implementaba ambos flujos pero la vista no los exponía.
Añadidos a la cabecera los botones "Importar de proyecto" e "Importar CSV/Excel" y
sus modales:
- CSV/Excel: nombre + fichero, vista previa de campos detectados, crear; con enlace
  para descargar CSV de ejemplo.
- Otro proyecto: selección de proyecto accesible + checklist de plantillas a importar.

Tests: TemplateImportTest (importar de proyecto y desde CSV). Suite 81 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:18:21 +02:00
javierandClaude Opus 4.8 9fc5854bbb fix(templates): la página de plantillas no embebía el componente
resources/views/projects/templates.blade.php usaba `<livewire/template-manager>`
(sintaxis inválida; tras la reorganización el alias es `inspections.template-manager`)
y un botón suelto con `$emit` (Livewire 2, fuera de componente) que no hacía nada.
Ahora embebe correctamente <livewire:inspections.template-manager> (que ya trae su
botón "Nuevo" + formulario funcional) y se elimina el botón roto.

Tests: TemplateCreationTest (la página embebe el componente; crear plantilla funciona).
Suite 79 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:53:51 +02:00
javierandClaude Opus 4.8 484351459a feat(templates): accesos a la página de plantillas de inspección
No había forma de llegar a projects.templates. Añadidos dos accesos, gateados con
@can('edit projects') (mismo permiso que protege la ruta):
- Dashboard del proyecto: botón "Plantillas" junto a "Mapa".
- Mapa: enlace "Gestionar plantillas" junto al selector de plantilla.

Test: TemplatesAccessTest (403 sin permiso, 200 con edit projects). Suite 77 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:45:03 +02:00
javierandClaude Opus 4.8 9bd6fd898d refactor(migrations): consolidar add_* en sus create_* (49 → 28)
Pliega las migraciones de "añadir/modificar columna" dentro de la migración create
de cada tabla, con esquema final idéntico (verificado por diff normalizado de columnas
+ índices y suite completa):
- users (locale, notes), projects (reference/external_reference_1/country),
  companies (apodo/estado/logo_path), layers (color; sin geojson_data),
  phases (fechas), features (status/responsible_user_id + sync + softDeletes),
  inspection_templates (phase_id), inspections (workflow + sync + softDeletes),
  issues (type + sync), progress_updates/media (sync), issue_tasks (overdue),
  permissions (group/description) y roles (description); soft-deletes en sus creates.
- Se mantienen las que dependen de orden/FK: add_profile_fields (users.company_id → companies)
  y update_inspections_feature_id_foreign (feature_id → features).
- Eliminado database/schema/mysql-schema.sql (obsoleto): las instalaciones nuevas
  ejecutan las migraciones; las BD existentes no se tocan. Regenerable con schema:dump.

Suite 75 passing. Esquema SQLite idéntico antes/después.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:40:54 +02:00
javierandClaude Opus 4.8 2dccbe3a14 feat(map): filtros por columna en cabecera (elementos e inspecciones)
Usa la cabecera secundaria de Rappasoft (setSecondaryHeaderEnabled + secondaryHeaderFilter):
- Elementos: Elemento (texto), Capa (select), Fase (select).
- Inspecciones: Fecha (date), Elemento (texto), Resultado (select), Usuario (select);
  Plantilla sin filtro.

Tests: MapTablesTest amplía con filtro de capa funcional. Suite 75 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:56:40 +02:00
javierandClaude Opus 4.8 25a59e4413 feat(map): listas de elementos e inspecciones con tablas Rappasoft
- FeatureTable e InspectionTable (DataTableComponent) sustituyen las tablas HTML
  de las pestañas "Elementos" e "Inspecciones" del mapa: búsqueda, orden, filtro
  (progreso) y acciones.
- Selección de elemento e "ver inspección" se comunican al ProjectMap por eventos
  (map-select-feature / map-view-inspection, vía #[On]); seleccionar abre el panel
  de edición y centra el mapa igual que antes.
- Las relaciones requieren sus FKs en additionalSelects (layer_id / feature_id,
  template_id, user_id) porque Rappasoft no selecciona '*'.

Tests: MapTablesTest (3). Suite 74 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:44:20 +02:00
javierandClaude Opus 4.8 624260961b chore: incluir rediseño del dashboard por proyecto (cabecera) y Manual.docx
Cambios de trabajo confirmados como funcionales:
- project-dashboard.blade.php: cabecera reorganizada (estado + accesos a mapa/gantt/incidencias).
- docs/Manual.docx: documento del manual generado.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:31:34 +02:00
javierandClaude Opus 4.8 7e997bc6aa feat(home): dashboard ligero renombrado a "Home"
- Sustituye el panel saturado de estadísticas por una vista "Home" centrada en lo
  del usuario: mis proyectos (compacto), mis tareas pendientes, incidencias asignadas
  y notificaciones recientes. Sin inundar de detalle (mejor con muchos proyectos).
- Etiqueta "Dashboard" -> "Home" en el menú y la cabecera.
- Elimina el orderByRaw FIELD() (solo MySQL) del antiguo dashboard.
- ExampleTest: ahora valida la conducta real (la raíz redirige a login).

Suite: 71 passing (0 fallos; resueltas las 2 pre-existentes de SQLite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:24:30 +02:00
javierandClaude Opus 4.8 7d390872c3 refactor(livewire): organizar componentes y vistas por dominio en subnamespaces
- app/Livewire: 34 componentes agrupados en Issues/, Projects/, Phases/,
  Companies/, Users/, Admin/, Inspections/, Layers/, Media/, Common/
  (Client/, Reports/, Forms/, Actions/ ya estaban). Namespaces actualizados.
- resources/views/livewire: vistas sueltas movidas a subcarpetas espejo
  (companies/, users/, phases/, roles/, inspections/, media/, common/);
  render() actualizado.
- Referencias actualizadas sin romper nada: rutas (FQN, nombres de ruta intactos),
  tags <livewire:...>/@livewire() a alias con punto, y use de los tests.
- No tocado: Volt de Breeze (auth/profile/navigation), y el portal cliente
  (user-nav/client-projects) que ya tenía referencias inconsistentes.

Verificado: 69 rutas OK, vistas compilan, suite 69 passing (solo 2 pre-existentes
sqlite). autoload regenerado con --ignore-platform-reqs (PHP 8.2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:54:09 +02:00
javierandClaude Opus 4.8 9c164bb7ef docs(manual): portada + índice navegable + huecos de captura; copia en inglés (MANUAL.en.md)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:50:39 +02:00
javierandClaude Opus 4.8 4f66175406 docs: manual del programa (instalación, uso por secciones, roles/permisos, flujos, API, FAQ)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:42:06 +02:00
javierandClaude Opus 4.8 f2b2583e62 docs(mobile): brief de traspaso para la app móvil (endpoints, payloads, modelo de datos, arquitectura offline)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:26:54 +02:00
javierandClaude Opus 4.8 480dfc657f feat(projects): usuarios/empresas del proyecto como tablas Rappasoft + permiso assign companies
- Permiso 'assign companies' propio (antes empresas reutilizaba 'assign users');
  concedido a los roles que ya tenían 'assign users'.
- ProjectUsersTable y ProjectCompaniesTable (Rappasoft): búsqueda, filtro por rol,
  cambio de rol en línea y quitar; gateadas por assign users / assign companies.
- ProjectUsers/ProjectCompanies quedan como contenedor (form de asignación) que
  embebe la tabla y refresca el desplegable vía eventos.
- Unificadas confirmaciones (wire:confirm) y notificaciones (dispatch notify).

Tests: ProjectAssignmentsTest (4). Suite 69 passing (solo 2 pre-existentes sqlite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:22:59 +02:00
javierandClaude Opus 4.8 c378ab5884 feat(phases): modal crear/editar fase + tabla Rappasoft
- PhaseList pasa a contenedor: botón "Agregar fase" + modal crear/editar con todos
  los parámetros (nombre, descripción, orden, color, progreso, fechas previstas y
  reales) y validación. Antes "Agregar fase" creaba directamente 'Nueva fase'.
- PhaseTable (Rappasoft): orden, nombre+descripción, barra de progreso, fechas,
  color y acciones (editar abre el modal vía evento, actualizar progreso, eliminar);
  búsqueda y ordenación. Gateado por 'manage phases' + acceso al proyecto.

Tests: PhaseManagementTest (4). Suite 65 passing (solo 2 pre-existentes sqlite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:56:05 +02:00
javierandClaude Opus 4.8 3d0f4d5cad feat(issues): tipo/categoría de incidencia (defecto/seguridad/calidad/documentación/otro)
- Issue::TYPES + typeLabels() (ES) + accessors type_label/type_color; columna type
  (string, default 'other') + fillable.
- IssueForm: select "Tipo de incidencia" con validación/carga/guardado.
- IssueTable: columna Tipo (badge) + SelectFilter por tipo.
- IssueDetail: badge de tipo en la cabecera.
- Sync offline: issue.create/update aceptan type; bundle (mapIssue) lo incluye.

Tests: IssuesEnhancementsTest (create muestra el campo vía HTTP, edición persiste) +
MobileApiTest (create con type). Suite 61 passing (solo 2 pre-existentes sqlite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:30:54 +02:00
javierandClaude Opus 4.8 19e1f57983 feat(users): idioma por defecto con banderas SVG + conmutador coherente
- Campo "Idioma por defecto" al crear/editar usuario (columna locale ya existente),
  como desplegable Alpine con banderas SVG reales (no emoji, que en Windows se ven
  como "ES"/"GB") servidas localmente: public/images/flags/{es,gb}.svg.
- User: locale añadido a fillable. UserForm: propiedad/validación/guardado de locale.
- LanguageSwitcher de la cabecera usa las mismas banderas SVG.
- Regla CSS [x-cloak] en el layout para evitar parpadeo de desplegables Alpine.

Tests: UserLocaleTest (2) — crear/editar persisten el idioma.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:10:27 +02:00
javierandClaude Opus 4.8 8c774d075d feat(issues): notificaciones, plantillas de checklist, alertas de vencimiento y reporte desde el mapa
- Notificaciones (DB): asignación de incidencia (IssueAssigned), asignación de tarea
  (IssueTaskAssigned), comentario (IssueCommented) y cambio de estado
  (IssueStatusChanged) a reporter+asignado excluyendo al actor.
- Plantillas de checklist: tabla issue_checklist_templates + modelo, gestor CRUD
  (IssueChecklistManager, ruta projects.issues.checklists) y "Aplicar plantilla" en
  el detalle (alta masiva de tareas).
- Alertas de vencimiento: columna overdue_notified_at + scope overdue, comando
  issues:notify-overdue (programado a diario) que avisa al asignado una sola vez;
  badge "vencidas" en la tabla y resaltado por tarea en el detalle.
- Reporte desde el mapa: botón "Incidencia" en el panel del feature seleccionado →
  formulario con feature pre-vinculado (IssueForm lee ?feature=).

Tests: IssuesEnhancementsTest (7). Suite 57 passing (solo 2 pre-existentes sqlite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 12:51:41 +02:00
javierandClaude Opus 4.8 3f240e5277 feat(issues): incidencias enriquecidas (tareas/comentarios/fotos/verificación) + tabla Rappasoft + logo
Web:
- IssueTask + IssueComment (modelos, migraciones, soft-deletes, campos de sync).
  Issue gana tasks()/comments() y accessor de % de avance derivado de tareas.
- IssueDetail (página): checklist con asignado/fecha límite/progreso, hilo de
  comentarios con foto por comentario, galería de fotos de la incidencia y flujo
  de verificación open→in_review→resolved/closed (+reabrir) con notas.
- Creación/edición en páginas propias (IssueForm), sin modal; al guardar redirige
  al detalle. Rutas projects.issues.create/edit/show.
- Listado con tabla Rappasoft (IssueTable): filtros por estado/prioridad, búsqueda,
  barra de progreso y acciones por fila gateadas por permisos; IssueManager queda
  como contenedor (cabecera + stats) que embebe la tabla.
- Seguridad: pertenencia al proyecto + permisos por acción (view/create/edit/delete
  issues, upload/delete media) en todos los componentes.

API móvil (offline):
- /sync: issue_task.create/update y issue_comment.create (idempotente, LWW).
- /media: parent_entity issue_task / issue_comment.
- bundle + tombstones incluyen issue_tasks / issue_comments.
- openapi.yaml + MOBILE_SYNC_PROTOCOL.md actualizados.

Tests: MobileApiTest 23 passing (+5); IssuesTablePageTest (3) smoke de la tabla.

Branding: logo RTE International — MAI Group (public/images/logo-rte.png) en login
y navegación; application-logo pasa de SVG por defecto a <img>.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 12:12:39 +02:00
javierandClaude Opus 4.8 14758136b6 feat(api): mobile API Milestones 5+6 — media upload, sync_logs idempotency, OpenAPI
Milestone 5 (media):
- POST /api/v1/media — multipart upload, attaches to feature/issue/project/
  phase/layer, idempotent by uuid, authz member + 'upload media'. Added
  uuid+client_updated_at to media.
- Bundle now includes a 'media' array (URLs) for the project's project/feature/
  issue attachments (delta-aware).

Milestone 6 (hardening + docs):
- sync_logs table/model: every applied op is logged; /sync short-circuits on a
  repeated op uuid -> 'duplicate' (true idempotency for updates too, not just
  creates).
- Rate limiting on login (10/min), sync (60/min), media (120/min).
- docs/openapi.yaml: OpenAPI 3 contract for the mobile team.

Tests: 18 passing (added media upload idempotency + sync_logs idempotency).
The mobile API (Milestones 1-6) is now feature-complete on the webapp side.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:23:50 +02:00
javierandClaude Opus 4.8 9d2b63c8f4 feat(api): mobile API Milestone 4 — full PUSH (inspections/issues/features + conflicts)
SyncController now handles the full mutation vocabulary:
- inspection.create (idempotent by uuid; project/layer derived from feature;
  authz member + 'create inspections'; status defaults to completed).
- issue.create (idempotent; authz member + 'create issues').
- issue.update (by server id; authz member + 'edit issues'; sets resolved_at
  when resolved/closed; last-write-wins conflict).
- feature.update (by server id; authz member + 'update progress'; recomputes
  phase progress; last-write-wins conflict).
- Conflict detection: client_updated_at vs server updated_at → returns
  'conflict' with the current server value.
Added uuid + client_updated_at to features/inspections/issues (guarded migration)
and their fillables. Tests: 16 passing (added inspection/issue/feature + conflict).

Note: 2 PRE-EXISTING test failures remain (not from this work, sqlite-only):
ExampleTest expects '/'=200 (app redirects), and the dashboard route uses MySQL
FIELD() which sqlite lacks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:09:34 +02:00
javierandClaude Opus 4.8 b5deb1c53a feat(api): mobile API Milestone 3 — full PULL (delta, tombstones, templates)
ProjectApiController bundle now supports incremental sync:
- ?since=<ISO8601> returns only records changed after that time (phases, layers,
  features, inspections, issues, templates), each filtered by its own updated_at.
- 'deleted' tombstones (soft-deleted ids since 'since') for phases/layers/
  features/inspections/issues so the device can purge locally.
- Bundle now also includes inspections, issues and inspection templates
  (with version + content hash for incremental template download).
- New GET /api/v1/templates (accessible projects, ?since= delta).
Tests: 12 passing (added delta, tombstones, templates cases). Note: the 'since'
query param must be URL-encoded by clients (ISO8601 '+' offset).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 09:45:18 +02:00
javierandClaude Opus 4.8 17a824f925 feat(api): mobile API Milestone 1+2 — Sanctum auth + offline sync vertical slice
Milestone 1 (auth foundation):
- Installed laravel/sanctum; HasApiTokens on User; published config + migration.
- routes/api.php with /api/v1; Sanctum 'ability' middleware alias registered.
- AuthController: POST login (long-lived revocable device token w/ ability
  mobile-sync + devices table), GET me, POST logout. New Device model/table.

Milestone 2 (vertical slice, offline-first):
- progress_updates: +uuid (client-generated) +client_updated_at.
- ProjectApiController: GET projects (accessibleBy), GET projects/{id}/bundle
  (project/phases/layers/features, membership-authorized).
- SyncController: POST sync — batch ops, idempotent by uuid, per-op result
  (applied/duplicate/error), server-set user_id, authz by permission+membership.
  Currently handles progress_update.create.

Tests: tests/Feature/Api/MobileApiTest (9 passing) — auth, accessible projects,
bundle authz, sync apply+idempotency, permission enforcement.

Also fixed a latent schema bug: projects.reference (and external_reference_1)
existed in the live DB but had no migration — added a guarded migration so fresh
installs match production.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 09:05:20 +02:00
javierandClaude Opus 4.8 ba363e7e18 docs: mobile offline-first sync protocol (Sanctum API tokens)
Approved plan/protocol for connecting a mobile app to the webapp: offline-first
with device outbox, PULL (bundle/delta/versioned templates/tombstones), PUSH
(/api/v1/sync idempotent by client uuid), media via multipart, conflict policy,
schema additions, security, and phased webapp deliverables. Auth decided:
Laravel Sanctum API tokens. No implementation yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:36:35 +02:00
javierandClaude Opus 4.8 13f36e8ec0 feat(authz): per-route permission gating for /admin (granular admin roles)
Finishes Phase 2: the /admin route group no longer requires 'manage all'
globally. Each route is gated by its specific permission so a non-super-admin
role can be granted partial admin access:
- /admin/users (+show) -> can:view users; create -> can:create users;
  edit -> can:edit users
- /admin/roles, roles/*, permissions -> can:manage roles
- Aligned the role screens' mount checks (RoleForm/RoleView/RolePermissionManager)
  from 'manage all' to 'manage roles'.
- Nav 'Administrator' link now shows on can('view users').
Admins keep full access via Gate::before (manage all). Closure routes
(users/roles lists) are now protected at the route level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:15:58 +02:00
javierandClaude Opus 4.8 8025fa6d05 refactor(authz): Phase 2 — replace hasRole('Admin') with permission checks
Permissions now actually govern access instead of the hard-coded Admin role:
- Super-admin bypass (see all projects / full access) -> can('manage all')
  in Project::scopeAccessibleBy, ProjectMap, ProjectDashboard, PhaseGantt,
  LayerManager, ProjectReportController.
- Redundant '|| hasRole(Admin)' fallbacks dropped (Gate::before already lets
  manage-all through can()): LayerManager (upload/delete layers), MediaManager
  (upload), ProjectMap (update progress), ProjectUsers/ProjectCompanies
  (assign users).
- Admin-only screens now gated by the matching permission: AdminUsers/UserView
  -> can('view users'), UserForm -> can('create users')|can('edit users'),
  CompanyView -> can('view companies').
- MediaManager delete: can('delete media') OR owner.
- Kept UserForm's domain guard (can't remove your own Admin role).

Note: the /admin route group still has middleware can:manage all, so admin
screens stay super-admin-only until that group is relaxed per-route.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:10:23 +02:00
javierandClaude Opus 4.8 efccb67635 feat(user-view): add Details (Ficha) tab as default with basic info + access validity
New 'Ficha' tab (first, default) on the user view: basic info card
(name/username/email/phone/address/member since) plus the 'Validez de acceso'
card and the Empresa card, moved here from the Permissions tab. The Permissions
tab now focuses on roles + the direct-permissions form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:02:05 +02:00
javierandClaude Opus 4.8 0120c4bfb8 feat(roles/users): add-user form on role view + per-user direct permissions form
1. Role view (Details tab): a small form to add users to the role (select of
   users not yet in the role + Add) and a per-row remove button. Uses
   assignRole/removeRole.
2. User view (Permissions tab): the same grouped, collapsible permissions form
   with switches — operating on the user's DIRECT permissions
   (givePermissionTo/revokePermissionTo). Permissions inherited from a role show
   as checked+disabled with a 'from role' tag; per-group All/None too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:51:59 +02:00
javierandClaude Opus 4.8 7f20399337 feat(roles): collapsible permission groups with check/uncheck-all + single-column switches
Permissions tab in the role view:
1. Each section is now a collapsible card (Alpine, chevron rotates).
2. Section header has 'All' / 'None' buttons (setGroup grants/revokes every
   permission of that group for the role; Admin keeps 'manage all').
3. Permissions render in a single column: name+description on the left, control
   on the right.
4. Controls are DaisyUI toggle switches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:44:58 +02:00
javierandClaude Opus 4.8 433c15a183 feat(permissions): full permission catalogue grouped by section
- Migration: add 'group' and 'description' columns to the permissions table.
- PermissionCatalogSeeder (idempotent updateOrCreate): full catalogue across 11
  sections — Proyectos, Fases y progreso, Capas y elementos, Inspecciones,
  Incidencias, Empresas, Usuarios, Roles, Informes, Archivos, General. Sets
  group + description on existing and creates the new ones; does NOT touch role
  assignments. Registered in DatabaseSeeder.
- RoleView: group permission toggles by the 'group' column in a defined section
  order and show each permission's description.
DB updated locally (migrate + seed run).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:32:16 +02:00
javierandClaude Opus 4.8 5587026446 feat(roles): Rappasoft list, slim create form, and 2-tab role view
1. Roles list now uses a Rappasoft table (RoleTable): search/sort, per-row
   view/edit/delete, and built-in bulk selection + 'Delete selected'. The
   /admin/roles page is a plain view embedding <livewire:role-table />.
   RoleForm create/edit now only has Name + Description (permissions removed).
2. New RoleView page (/admin/roles/{role}) with two tabs:
   - 'Details': header with role name + Back button; description with Edit/Delete
     buttons; table of users holding the role (avatar+name | last name | status).
   - 'Permissions': all permissions grouped by section (by resource), each with a
     toggle switch to grant/revoke for this role (Admin keeps 'manage all').
Removed the old RoleManager component/view (superseded).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:21:16 +02:00
javierandClaude Opus 4.8 5092896a1e refactor(roles): role create/edit as a full page instead of a modal
Per feedback, 'New role' (and Edit) now open a dedicated page instead of a
modal:
- New RoleForm full-page component + view at /admin/roles/create and
  /admin/roles/{role}/edit (name, description, permission checkboxes; saves
  and redirects back to the list).
- RoleManager trimmed: the create/edit modal and its logic removed; 'New role'
  and the per-row/view-modal Edit are now links to the new pages.
- Kept the read-only View modal, single + bulk delete, and protections.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:05:01 +02:00
javierandClaude Opus 4.8 938e704a67 feat(roles): role CRUD screen (list + form name/description + view/edit/delete + bulk)
Per request:
- Migration: add nullable 'description' to the roles table.
- RoleManager Livewire component + view at /admin/roles:
  * Roles list table with per-row checkboxes for bulk selection (+ select-all)
    and a 'Delete selected' bulk action (protected roles skipped).
  * 'New role' opens a modal form with just Name + Description (and permission
    checkboxes to assign).
  * Per-row View / Edit / Delete buttons (View modal shows description,
    counts and assigned permissions).
- Admin role stays protected (no rename/delete/lose 'manage all').
- /admin/users links to the new Roles screen; the phase-1 permission matrix
  stays available via a 'Matrix view' link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:57:59 +02:00
javierandClaude Opus 4.8 828e70fbe2 feat(permissions): admin role/permission matrix + Gate::before super-admin
Phase 1 (additive, doesn't touch existing checks):
- Gate::before grants everything to holders of 'manage all' (the Admin role),
  robustly (returns true/null, never false; swallows missing-permission).
- New RolePermissionManager Livewire component + view at /admin/permissions:
  editable Roles x Permissions matrix (toggle saves instantly), create/delete
  roles, create/delete permissions. Admin role and 'manage all' are protected.
- Link to the screen from /admin/users header.
Roles are editable from the UI as chosen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:39:28 +02:00
javierandClaude Opus 4.8 da0c8bd134 fix(auth): register Spatie role/permission middleware + add missing #[Layout] (fixes post-login crash)
Login authenticated fine but the landing page crashed (so it looked like
'login doesn't work'):
- bootstrap/app.php didn't register Spatie's middleware aliases -> any route
  with role:/permission: threw 'Target class [role] does not exist'.
  Registered role / permission / role_or_permission.
- config/livewire.php absent -> default layout is the non-existent
  components.layouts.app. ProjectList, PhaseProgress and ReportsDashboard
  lacked #[Layout('layouts.app')] -> MissingLayoutException. Added it (the
  other 10 routed components already had it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:12:20 +02:00
javierandClaude Opus 4.8 316e0ede39 fix(routes): remove dead /logout route + dead Auth controller imports
web.php referenced App\Http\Controllers\Auth\AuthenticatedSessionController
(and imported 8 other Auth\* controllers) that don't exist — this is a
Breeze+Volt app where auth is handled by Volt pages (routes/auth.php) and
logout by the Volt navigation action (App\Livewire\Actions\Logout).

The broken /logout route made 'php artisan route:list' throw
ReflectionException. Removed the dead route (nothing uses route('logout');
the nav uses wire:click=logout) and the unused Auth controller imports.
Login/register/reset already worked via Volt; logout works via the Volt action.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:38:28 +02:00
javierandClaude Opus 4.8 564b433a62 style: manual UI tweaks (users/companies headers, wider layout, user-view, projects index)
User's manual changes: header slots with New-user/New-company actions, wider
max-w-7xl containers on /admin/users and /companies, plus tweaks to
user-view and projects index views. All views compile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:33:02 +02:00
javierandClaude Opus 4.8 7df6d208d9 feat(issues): build the rich Issues screen (recover yesterday's draft)
Rebuilt IssueManager to match the 403-line draft view that had no companion
component:
- Modal create/edit form (title, description, priority, status, assignee,
  resolution notes shown when resolved/closed)
- Stats bar (open/in_review/resolved/closed/total) and a styled table
- New methods: openForm/closeForm, resolve, close (+ existing save/delete),
  projectUsers for the assignee dropdown, resolved_at kept in sync with status
- render() now points to livewire.issues.issue-manager; deleted the old
  89-line stub livewire/issue-manager.blade.php
The Issue model already had everything (resolution_notes, resolved_at,
priority_color/status_color accessors, reporter/feature/assignee relations).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:16:14 +02:00
javierandClaude Opus 4.8 860c502f32 chore: remove obsolete duplicate views/components (superseded code)
Deleted (all superseded, recoverable in git history):
- resources/views/projects/edit.blade.php + ProjectController@edit()
- resources/views/projects/create.blade.php + ProjectController@create()
  (projects.create/edit routes point to the Livewire ProjectForm; these
   controller methods were excluded from the resource and never invoked)
- app/Livewire/ProjectEditTabs.php + project-edit-tabs.blade.php
  (old tabbed editor, functionality recovered inside ProjectForm)
- app/Livewire/LayerUpload.php + layer-upload.blade.php (superseded by LayerManager)

Kept resources/views/livewire/issues/issue-manager.blade.php as a reference
for the future rich Issues screen (its companion component was never built).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:11:23 +02:00
javierandClaude Opus 4.8 8101f22413 refactor: migrate users/companies pages to Rappasoft tables; remove unused welcome view
Point 2 (migrate to Rappasoft tables):
- /admin/users now renders <livewire:user-table /> (+ a New user button)
  instead of the custom admin-users component
- /companies now renders <livewire:company-table /> (+ New company button)
  instead of the hand-rolled card list

Point 3 (delete): removed resources/views/welcome.blade.php (unused — '/'
redirects to dashboard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:09:15 +02:00
javierandClaude Opus 4.8 fe57388f05 feat(project-form): wire the rich data form (labels-left) + edit tabs
The edit/create project page used a stripped-down inline form. Rewired it
to the existing-but-orphaned pieces:
- Project Data uses the rich partial project-data-form (labels-left/field-right
  layout, sections Identification/Location/Planning, address search + Leaflet
  map with draggable marker + reverse/forward geocoding, country dropdown)
- When editing, tabs added for Phases / Users / Companies (nested Livewire
  components phase-list / project-users / project-companies)
- ProjectForm now provides $countryList (the partial's country dropdown needs it)
- Added the map JS the partial was missing: inits #project-location-map, search
  box, and calls $this->setLocation(lat,lng,address,country) so the wire:model
  fields update

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 13:42:42 +02:00
javierandClaude Opus 4.8 75c07aa0d4 fix(project-form): use $project instead of undefined $projectId in blade
project-form.blade.php referenced $projectId, but the ProjectForm component
exposes $project (the model, null when creating). Caused 'Undefined variable
$projectId' on /projects/{id}/edit. Switched both usages to $project.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 13:20:37 +02:00
javierandClaude Opus 4.8 558b1732aa feat(project-map): clearer editor tabs + per-phase and per-layer visibility
1. Editor tabs restyled as spaced DaisyUI buttons (btn-primary when active,
   btn-ghost otherwise) — fixes cramped labels and missing active indicator.
2. Layer visibility now works at two levels:
   - Phase toggle calls togglePhase() and shows/hides ALL its layers
     (checked only when every layer of the phase is active)
   - Each layer has its own independent toggle calling toggleLayer()
   Map JS regrouped to build one Leaflet group per LAYER (keyed by layer id)
   instead of per phase, so activeLayers (layer ids) drives visibility
   correctly per layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 13:14:17 +02:00
javierandClaude Opus 4.8 19fef5aa25 fix(project-map): raise inspection modal z-index above map panel
The DaisyUI .modal default z-index (999) was below the phases/layers panel
(z-1000) and its reopen button (z-1001), so they showed on top of the modal.
Set the modal to z-[2000].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 13:02:27 +02:00