Commit graph

72 commits

Author SHA1 Message Date
Cursor Origin
d99dcd8ae7 Pre-computed merge for change #26 1970-01-01 00:00:00 +00:00
Cursor Agent
05005d5a9d
fix(iam): asignar documents.delete al rol tenant_admin del sistema
Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-16 21:11:20 +00:00
Cursor Agent
191d0fda3d
fix(padron): archivar todas las versiones al reemplazar documento por tipo
Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-16 21:11:13 +00:00
Cursor Agent
17d9393b9e
fix(padron): vista previa inline, eliminar y reemplazar documentos
- Sirve documentos con Content-Type real e inline por defecto (query disposition)
- DocumentPreviewDialog reconstruye blob con MIME correcto para PDF/imagen
- RPC y DELETE para eliminar documentos del expediente; botón en UI
- Permiso IAM documents.delete; store archiva todas las versiones previas del tipo
- Limpia FileUpload tras subir; is_current robusto en listas vigente/histórico

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-16 21:11:11 +00:00
Alberto Martinez
cb04a9a04c fix: alta de proyecto sin choque en projects_code_key (PRY duplicado) (#25)
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Causa

El error `duplicate key value violates unique constraint "projects_code_key"` aparece porque:

1. La columna `projects.code` es **UNIQUE en toda la base** (todos los tenants).
2. Al dar de alta, `fn_project_create` llama a `fn_next_project_code`, que hacía `MAX(PRY…)+1` **solo sobre las filas que el tenant ve** (RLS).
3. Si en la base ya existe `PRY-0001` (seed de dev, otra empresa cliente, u otra obra que este tenant no ve), el sistema vuelve a proponer `PRY-0001` → el `INSERT` falla con CONFLICT.

No es el nombre del proyecto ni el presupuesto adjunto: es el **código autogenerado**.

## Cambio (Liquibase 039)

- `fn_next_project_code`: `SECURITY DEFINER` para leer la secuencia **global** de códigos PRY/OBR.
- `fn_project_create`: `pg_advisory_xact_lock` al asignar código (evita duplicado si envían dos veces seguidas).

## Cómo probar

1. Aplicar migraciones (039).
2. Con un tenant que aún no tenga obras (o en un ambiente donde ya exista `PRY-0001` de otro tenant), dar de alta un proyecto nuevo.
3. Debe crearse con `PRY-0002` (o el siguiente libre), sin toast de CONFLICT.

Script de demostración RLS: `scripts/verify-project-code-next.sql` + `verify-project-code-next-run.sql`.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-d8a5d34a-ead9-4912-8296-322cff799783?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-d8a5d34a-ead9-4912-8296-322cff799783&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-16 20:56:22 +00:00
Alberto Martinez
17c40c66a6 feat: ver el programa de obra como Gantt (no la grilla de ceros)
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Qué pasaba

El Excel que importaste es **por partida** (erogaciones semanales). Eso llena **Partidas** y el **Gantt**, no las barras de concepto. La pestaña que se abría era **Conceptos**, con 0–0 en cada semana.

Además el Gantt que ya existía tenía el encabezado de semanas mal alineado.

## Cambio

- **Gantt es la primera pestaña** y se abre sola si el programa es solo por partida.
- Cada partida es una barra de la primera a la última semana con monto (tooltip con total).
- En Conceptos, si no hay barras, se explica que hay que mirar Gantt o Partidas.

## Cómo probar

1. Mergear y recargar (solo front; no hay Liquibase).
2. Abrir Programa de obra en la obra ya importada: debe entrar en **Gantt** con barras (A001, A002, …) sobre Sem 1–9.
3. Partidas: montos semanales (no ceros).
4. Conceptos: sigue en 0 y el aviso; es normal en este Excel.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-d8a5d34a-ead9-4912-8296-322cff799783?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-d8a5d34a-ead9-4912-8296-322cff799783&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 22:12:23 +00:00
Alberto Martinez
0045ccd8ed fix: programado vs costo (warehouse_movements.project_id no existe)
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Problema

Tras cargar el programa, la pantalla pide en paralelo get / concepts / partidas / **vs-cost**. El vs-cost fallaba:

`fn_work_program_vs_cost (sqlstate=42703): column m.project_id does not exist`

`warehouse_movements` no tiene `project_id`. El proyecto está en `warehouses.project_id`.

## Cambio (changeset 038)

- Salidas de almacén por `JOIN warehouses w ON w.id = m.warehouse_id WHERE w.project_id = …`
- Ejecutado por partida **sin duplicar** al cruzar varias semanas (el join anterior multiplicaba el monto)

## Prueba local (hecha antes de pedir merge)

En Postgres 16 corrí las **4 RPCs** que usa la página, con presupuesto, 2 semanas, gasto $50 y salida de almacén $25:

- `fn_work_program_get` — curva 400 / 600
- `fn_work_program_list_concepts` — items
- `fn_work_program_list_partidas` — A001
- `fn_work_program_vs_cost` — ejecutado semanal **75**, por partida **75** (no 150)

Script: `scripts/verify-work-program-load.sh`

## Cómo probar en tu ambiente

1. Mergear y aplicar Liquibase (038).
2. Recargar Programa de obra en la obra ya importada. Sin toast rojo.
3. La pestaña / bloque de programado vs costo debe abrir.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-d8a5d34a-ead9-4912-8296-322cff799783?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-d8a5d34a-ead9-4912-8296-322cff799783&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 21:55:34 +00:00
Alberto Martinez
7ffdd9f7bf fix: recargar programa de obra (v_row no es una tabla)
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Problema

Tras el #21 el GET ya no choca por agregados anidados, pero al recargar aparece:

`fn_work_program_get (sqlstate=42P01): missing FROM-clause entry for table "v_row"`

`v_row` es `jsonb`. Escribir `v_row.value` lo toma Postgres como `tabla.columna`, no como campo del JSON.

El #21 destapó este bug: el 42803 cortaba antes de llegar al loop.

## Cambio

Changeset **037** (no se edita el 036, ya corrido). La curva se arma en SQL (subquery + `SUM() OVER`) sin el `FOR` ni `v_row.value`.

## Cómo probar

1. Mergear este PR y aplicar Liquibase (037).
2. Abrir Programa de obra en la obra que ya importaste. Debe cargar sin toast rojo.
3. Recargar la página: igual.
4. Si hace falta, volver a Importar el Excel por partida y confirmar que se ve el programa.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-d8a5d34a-ead9-4912-8296-322cff799783?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-d8a5d34a-ead9-4912-8296-322cff799783&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 21:46:48 +00:00
Alberto Martinez
1a3fb49272 fix: guardar programa de obra (GET fallaba por agregados SQL anidados)
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Problema

La preview del Excel por partida ya funciona. Al pulsar **Importar** el programa sí se guarda (`Programa importado`), pero al recargar `GET /v1/projects/:id/work-program` aparece:

`Error inesperado en fn_work_program_get (sqlstate=42803): aggregate function calls cannot be nested`

Postgres no permite `jsonb_agg(... SUM(...))` en el mismo SELECT. Eso arma la curva semanal después del import.

## Cambio

Changeset `036`: `SUM` va en una subquery y `jsonb_agg` queda afuera. El import no cambia.

## Cómo probar

1. Aplicar migraciones Liquibase (changeset 036).
2. En una obra con presupuesto, importar el Excel por partida (el de 8 partidas / 9 semanas).
3. Tras **Importar**, la pantalla debe mostrar el programa (semanas y curva), sin el toast rojo.
4. Actualizar / volver a entrar a Programa de obra: sigue cargando.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-d8a5d34a-ead9-4912-8296-322cff799783?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-d8a5d34a-ead9-4912-8296-322cff799783&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 21:36:05 +00:00
Alberto Martinez
4750dab95f Unir fixes de las pruebas (PDF, presupuesto, programa, gastos, almacén, documentos, gafete, nómina)
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
Unifica los PRs de las pruebas de hoy en una sola rama contra `main`, con el changelog de Liquibase ya reconciliado.

**Incluye**
- #11 ver PDF de proyecto (`doc_id`)
- #12 toast al subir presupuesto que no es Excel
- #13 preview del programa de obra
- #14 ribbon de Control presupuesto (ya venía dentro de #15)
- #15 captura de gastos, proveedores y unidades
- #16 almacén global / abrir-cerrar / catálogo
- #17 documentos sin fecha de emisión ni vencimiento
- #18 gafete: `fn_badge_job_get` + empresa del tenant
- #19 asistencia y faltas L–S en Nómina

**No mergear los PRs sueltos** (#11–#19) si mergeas este: chocan en `changelog-master.xml` porque varios añadieron un changeset `032`. Aquí están todos incluidos con IDs distintos.

**Cómo probar (rápido)**
- Proyecto: abrir un PDF del expediente
- Presupuesto: subir un archivo que no sea Excel y ver el toast
- Programa de obra: importar Excel y ver preview
- Gastos: alta con PrimeVue y reclasificar sin concepto
- Almacén: global + abrir/cerrar obra + catálogo
- Padrón: subir documento sin fechas
- Gafetes: consultar/descargar job y ver el nombre de la empresa
- Nómina → Jornal: columnas L–S para marcar asistencia/falta

Mergea **este** PR. Después se pueden cerrar #11–#19.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-d8a5d34a-ead9-4912-8296-322cff799783?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-d8a5d34a-ead9-4912-8296-322cff799783&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 21:23:23 +00:00
Cursor Agent
ad152d46f4
merge: #18 gafete job y empresa del tenant
Añade 032-fn-badge-job-get al changelog junto a los demás 032.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 21:13:45 +00:00
Cursor Agent
5e4d9a6710
merge: #17 documentos sin fechas de vigencia
Añade 032-document-validity-from-upload junto a los demás 032.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 21:13:33 +00:00
Cursor Agent
15ffb3ca29
merge: #15+#16 gastos, proveedores y almacén
Conserva los changesets 032 ya unidos y añade 033–035.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 21:13:02 +00:00
Cursor Agent
9c198a3be7
merge: #19 asistencia y faltas en nómina
Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 21:12:49 +00:00
Cursor Agent
2f2d23004e
merge: #13 preview programa de obra
Incluye ambos changesets 032 (document-get y work-program) en el changelog.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 21:12:47 +00:00
Cursor Agent
202eca337b
merge: #12 toast presupuesto no Excel
Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 21:12:37 +00:00
Cursor Agent
016bc0bcee
merge: #11 ver PDF de proyecto
Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 21:12:37 +00:00
Cursor Agent
14985ae829
fix: filtrar asistencia del paquete de nómina por tenant
attendance no tiene RLS propio; el JOIN a projects ahora también
restringe por tenant_id para no mezclar marcas de otro cliente.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 20:59:27 +00:00
Cursor Agent
9c8b6ea19c
fix: devolver días L–S y celdas de asistencia en el paquete de nómina
La UI de Jornal ya tenía checkboxes por día, pero GET /v1/payroll/weeks
no enviaba days ni cells, así que las columnas L–S no se renderizaban
y solo se veía el total Días en cero.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 20:58:41 +00:00
Cursor Agent
31a7c1cbf5
fix: gafete usa la empresa del tenant y corrige fn_badge_job_get
El ORDER BY del listado de personas refería la tabla w fuera del
subquery (42P01). El frente del gafete ya no imprime Construcciones
Arctec/Zendala: usa el nombre comercial de la empresa del proyecto.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 20:52:35 +00:00
Cursor Agent
e89b51bf43
fix: subir documentos sin fecha de emisión ni vencimiento
La vigencia de 3 meses para alta o reingreso cuenta desde la fecha de
carga. Quien recibe el expediente revisa si el documento es válido.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 20:47:16 +00:00
Cursor Agent
af5bbd7566
fix: consolidar stock global en cliente si el API aún no agrupa
Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 20:42:13 +00:00
Cursor Agent
e528d217d3
feat: almacén global, abrir/cerrar por obra y catálogo de materiales
El árbol de Almacén deja de listar todas las obras. El almacén global
siempre está disponible y consolida el stock de los almacenes abiertos.
Las obras abren almacén a demanda y al cerrarlo pueden devolver el
remanente al global. Entradas y traspasos usan el catálogo de materiales
con unidades de medida y uno o varios proveedores.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 20:41:18 +00:00
Cursor Agent
61e36696b9
feat: reclasificar gastos sin concepto o cambiar de partida
Si un gasto se guardó sin clasificar, ahora se edita desde la lista (clic o Reclasificar) y se le asigna o cambia el concepto del presupuesto.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 20:19:13 +00:00
Cursor Agent
4b5d9572ee
fix: concepto de presupuesto con Select filtrable de PrimeVue
El buscador pasa a Select con filtro, el mismo patrón que proveedor y unidad, para que overlay y tema coincidan con el resto del formulario.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 20:13:06 +00:00
Cursor Agent
7a31453dfd
fix: overlay del buscador de concepto acotado al campo
El AutoComplete se estiraba con las descripciones largas y tapaba el diálogo. La lista queda al ancho del input, con clave + texto envuelto.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 20:11:56 +00:00
Cursor Agent
1e5bc22936
fix: catálogo de proveedores y unidades en la captura de gastos
Proveedor y unidad pasan a Select de catálogo (no texto libre). Subtotal, IVA y total se calculan al vuelo. El comprobante usa FileUpload de PrimeVue.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 20:06:45 +00:00
Cursor Agent
6fd673f581
fix: ribbon de Control presupuesto y captura de gastos por concepto
La pantalla de control era solo lectura, pero heredaba los botones de Programa de obra (no-op). Ahora tiene acciones propias, explica que el ejecutado viene de Gastos/almacén, y abre la captura ligada al concepto.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 19:55:37 +00:00
Cursor Agent
b7bd167e08
fix: importar programa de obra sin jsonb_object_length
Postgres no tiene jsonb_object_length; fn_work_program_replace
tronaba al confirmar el Excel. El conteo de semanas ahora usa
un contador en el loop de periodos.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 19:49:26 +00:00
Cursor Agent
b0a1a3147c
fix: mostrar preview del programa de obra al importar Excel
El Excel Neodata por partida sí se leía (A001–A009, 9 semanas), pero
la API devolvía { ok, data } sin envelope y el panel no desembalaba
data: el diálogo mostraba 0 y «Sin match». Ahora se aplana la
respuesta y el preview lista las partidas encontradas.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 19:44:35 +00:00
Cursor Agent
fa3656d142
fix: mensajes claros al subir presupuesto que no es Excel
El toast de error salía vacío porque el preview 400 no traía message.
Ahora solo se aceptan .xlsx/.xls, y si el Excel no tiene el formato
de presupuesto se explica qué columnas o plantilla usar.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 19:34:57 +00:00
Cursor Agent
de07975d4a
fix: abrir documentos de proyecto enviando doc_id a la RPC
La API pedía el PDF con { id } y fn_project_document_get leía
payload->>'doc_id', así que el id llegaba vacío aunque el archivo
ya estuviera en el bucket. Mismo desajuste en empresa y trabajador.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 19:27:24 +00:00
Alberto Martinez
9ea4706954 IAM UX: dialog fixes, auto role code, permission matrix
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Changes

### IAM dialog UX
- Checkbox / scope fixes in user edit dialog
- Auto-generated role code from label (no manual Código field)

### Permission matrix (detail panel)
Replaces the flat list of 50+ checkboxes with a structured matrix:

- **Grouped by module** (Presupuesto, Gastos, Nómina, Documentos de obra, etc.)
- **Short description** per module explaining what it controls
- **CRUD columns**: Ver / Crear / Editar / Eliminar
- **Tooltips** on each cell describing the specific permission
- **Bulk actions**: Solo ver, Limpiar, Todo / Solo ver per module
- **Search** by module or permission name
- **Plantilla rápida** dropdown (Operativo, Supervisor, Contador, …)

## Test

1. Usuarios IAM → Roles → select a tenant role (not system).
2. Detail panel shows grouped matrix instead of flat list.
3. Use module checkbox or **Todo** / **Solo ver** to toggle a whole area.
4. Hover a cell for tooltip; apply **Plantilla rápida** → **Guardar permisos**.
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-0da3751a-38ff-40ab-bba2-5df2322c8cda?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-0da3751a-38ff-40ab-bba2-5df2322c8cda&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 19:15:52 +00:00
Cursor Agent
1a50823368
IAM: grouped CRUD permission matrix with hints and bulk actions
Replace the flat checkbox list with modules grouped in a Ver/Crear/Editar/
Eliminar grid, module descriptions, search, view-only shortcuts, and quick
template apply.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 19:11:14 +00:00
Cursor Agent
2f7f121121
IAM: auto-generate role code from label when creating roles
Remove the manual Código field from the new-role dialog. Prefill label from
the selected template and derive a unique slug code on save.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 19:08:46 +00:00
Cursor Agent
d9aeee1073
IAM dialog: checkbox text no longer toggles the control
Replace label[for] captions with plain spans so warehouse and scope
options only change when clicking the checkbox itself.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 19:07:02 +00:00
Cursor Agent
4767676976
Fix scope MultiSelect toggling 'Todas las obras' checkbox
FormField wrapped slot content in a <label>, so clicks on the project
MultiSelect bubbled to the first control inside (scope_all_projects).
Use a div wrapper for multi-control field groups.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 19:05:19 +00:00
Alberto Martinez
c5f9ac0563 Fix IAM v2 Liquibase migrate failure on deploy
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Problem

Coolify deploy built all images successfully but failed when the `migrate` job exited with code 1. The IAM changeset `005-iam-v2-roles-crud.sql` had already applied through `005g` on production; it failed at `005h-swap-roles`.

## Root cause

Changeset `005h` used `splitStatements:true` with `endDelimiter:;` while containing a `DO $$ ... END $$` block. Liquibase splits on every semicolon, which breaks PL/pgSQL blocks and causes the migrate step to fail.

Secondary risks on production data:
- Users without `role_id` after backfill (missing per-tenant `operativo` role)
- Multiple `tenant_admin` users in the same tenant (unique partial index would fail)

## Fix

1. **Move the unique-index `DO` block** to a new changeset `005h2-unique-tenant-admin` with `splitStatements:false`.
2. **Add changeset `005g2-ensure-role-backfill`** (new, safe for DBs that already ran `005g`):
   - Create `operativo` roles for every tenant that has users
   - Re-backfill any remaining `role_id`
   - Demote duplicate tenant admins to `operativo` (keep lowest `id` per tenant)
   - Remove orphan users without `tenant_id`

## Deploy

Merge to `main` and redeploy in Coolify. No manual SQL should be required if the DB stopped at `005h` as expected.

If migrate still fails, capture logs with:

```bash
docker logs migrate-pdyrt8ccp804tfmutefau0k6-<latest> 2>&1 | tail -100
```
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-0da3751a-38ff-40ab-bba2-5df2322c8cda?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-0da3751a-38ff-40ab-bba2-5df2322c8cda&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 18:58:33 +00:00
Cursor Agent
a9fbca232d
Fix IAM v2 migrate: split DO block and harden role backfill
The migrate job failed at changeset 005h during Coolify deploy. Liquibase
was splitting the DO $$ block on inner semicolons (splitStatements:true),
which breaks PL/pgSQL. Move the unique-index DO block to 005h2 with
splitStatements:false.

Add 005g2 to create missing operativo roles per tenant, backfill any
remaining role_id values, demote duplicate tenant_admin users to operativo,
and remove orphan users without tenant_id before SET NOT NULL.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 18:57:19 +00:00
Alberto Martinez
0d660c4549 IAM clientes: roles por tenant, matriz CRUD y alcance
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary

Implements full client-side IAM: per-tenant custom roles, CRUD permission matrix, user/project/warehouse scope, and enforcement across API and web-panel.

## Key changes

- Auth with `role_id`, `is_owner`, status checks; API key without tenant_admin bypass
- IAM REST: users/roles CRUD, reset access, `/v1/me/permissions`, `/v1/me/scope`
- CRUD route gates + project/warehouse scope + project doc category filtering
- Rewritten `usuarios.vue` with ribbon, tree, matrix; dynamic menu via `usePermissions`
- Tests: `iam_test.ts` (9 passing); `deno check` and web-panel build green

Apply migrations `005-iam-v2-roles-crud.sql` and `031-project-doc-types-technical.sql` before deploy.
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-0da3751a-38ff-40ab-bba2-5df2322c8cda?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-0da3751a-38ff-40ab-bba2-5df2322c8cda&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 18:49:53 +00:00
Cursor Agent
aed2f4531b
Implement client IAM v2: roles per tenant, CRUD matrix, scope, and panel UI
- Add IAM v2 auth with role_id, is_owner, status checks, and API key without tenant_admin bypass
- Enforce CRUD permissions and project/warehouse scope across API routes
- Rewrite usuarios.vue with ribbon, tree, user/role management, and permission matrix
- Add dynamic menu, route guards, usePermissions/useScope composables
- Apply role templates on create; filter project docs by category; hide cost tab without permission
- Add iam_test unit tests; update bootstrap and SaaS tenant admin to use role_id

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 18:16:59 +00:00
Cursor Agent
25ceb6a6bd
Add IAM v2 roles/permissions migration and project doc types
Introduce Liquibase changeset 005 for IAM schema v2: CRUD permission codes,
tenant-scoped roles with system tenant_admin, role_permissions by role_id,
user scope fields and user_projects, RLS policies, and full RPC suite for
roles and users. Add core changeset 031 for technical project document types.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 18:10:45 +00:00
Alberto Martinez
7bcef22d59 Menú agrupado y ribbon tipo mockup
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
El menú principal deja de ser una lista plana que se parte en dos filas. Queda en una sola barra con iconos a la izquierda y grupos:

- Inicio
- Obra (Proyectos, Presupuesto, Programa de obra, Control costos)
- Operación (Gastos, Almacén, Kanban)
- Personal (Padrón, Gafetes, Nómina)
- Administración (Usuarios)

Configuración y empresas siguen en el menú del avatar. Ayuda duplicada se quitó del menú (queda el botón de la derecha).

El ribbon (Nuevo/Editar) se compacta como tarjeta: icono arriba, grupos, hover suave y Alta en azul.

Validación:
- `npm run build` en `web-panel` completó sin errores.
- Capturas del escritorio en `/proyectos`: menú en una fila, dropdown de Obra y ribbon de Acciones.
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-3755a070-b0cd-40ee-b616-7493e3911e5c?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-3755a070-b0cd-40ee-b616-7493e3911e5c&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 16:57:19 +00:00
Alberto Martinez
496273b41c feat: Programa de obra por proyecto
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary

Implementa el módulo **Programa de obra** por proyecto, vinculado al presupuesto ya existente.

### Flujo
1. Cargar presupuesto (requisito)
2. Definir fechas de inicio/término en el proyecto
3. Crear programa: **importar Excel Neodata/Opus**, **generar automático** o **crear vacío**
4. Editar en cuadrícula (conceptos con `Barra=` y partidas con erogaciones semanales)
5. Visualizar Gantt resumido y comparativo **programado vs ejecutado**

### Backend
- Tablas: `work_programs`, `work_program_periods`, `work_program_concept_slots`, `work_program_partida_amounts`, `work_program_progress`
- RPC: list/get, replace (import), generate (wizard), update celdas, recalculate partidas, vs-cost
- Parser Excel para formatos **por concepto** (`Barra=`) y **por partida** (montos semanales)
- Tests con fixtures reales del usuario

### Frontend
- Nueva página `/programa-obra` con pestañas: Conceptos, Partidas, Gantt, Programado vs costos
- Diálogos de importación y generación automática
- Menú principal, enlace en ficha de proyecto, banner en presupuesto

### Permisos
Reutiliza `manage_budget` en MVP.

## Test plan
- [x] `deno test api/work_program_excel_test.ts` (5 tests)
- [x] `deno check` API modules
- [x] `npm run build` web-panel
- [ ] Migrar DB (`029`, `030`) en entorno con Postgres
- [ ] Importar Excel por concepto y por partida en obra con presupuesto
- [ ] Generar automático con estrategia keyword
- [ ] Recalcular partidas desde conceptos
- [ ] Verificar tab Programado vs costos con gastos registrados
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-3985fe74-d26b-4506-bd4d-f9ad57374141?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-3985fe74-d26b-4506-bd4d-f9ad57374141&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 04:51:09 +00:00
Alberto Martinez
f4667dd325 fix(web-panel): corregir crash post-login y mejorar pantalla de error
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Problema
Tras iniciar sesión en producción (`panels.mrdev.mx`), el panel mostraba "No se pudo cargar el panel" con:
`TypeError: Cannot read properties of undefined (reading 'filter')`

## Causa
La API devuelve respuestas con envelope RPC (`{ ok, code, data, message, layer }`), pero pantallas legacy como el dashboard esperaban shape plano (`workers`, `projects`, etc.). Al asignar `workers.value = w.workers`, el valor quedaba `undefined` y los `.filter()` en computed fallaban.

## Solución
- Normalizar respuestas en `useApi` con `unwrapApiPayload()` para compatibilidad con pantallas existentes
- Endurecer `index.vue` con arrays por defecto y manejo de errores en carga
- Rediseñar la pantalla de error global: mensaje "¡Oops! Algo falló", código de error, resumen legible y detalle técnico colapsable

## Deploy
Reconstruir y desplegar `web-panel`. Si las migraciones `027`/`028` no están en prod, aplicar `./db/update.sh core iam`.
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-eeec3b5c-f789-43e8-a353-0755e4706377?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-eeec3b5c-f789-43e8-a353-0755e4706377&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-04 06:57:33 +00:00
Cursor Agent
1f85b44e80
fix(web-panel): unwrap API envelope post-login and improve error screen
- Normalize RPC responses in useApi so legacy pages receive flat shapes
- Harden dashboard index.vue against undefined arrays and load failures
- Redesign Nuxt error boundary with Oops message, error code, and retry

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-04 06:56:25 +00:00
Alberto Martinez
41273dfb04 Gastos, almacén y control presupuestal
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Validación de endpoints y SPs

Se probaron todos los endpoints nuevos (gastos, almacén, control presupuestal, IAM) y sus RPCs asociados.

### Bugs corregidos en migraciones/SQL

1. **IAM grants en schema core** — `iam-004e` intentaba `GRANT` sobre `core` con rol `iam_owner` (sin permiso). Los grants cruzados se movieron a `core-027-iam-rpc-cross-grants.sql`.
2. **`_cost_settings` ambiguo** — columnas `budget_warn_pct` etc. colisionaban con `RETURNS TABLE` en PL/pgSQL, rompiendo `fn_expense_create` y control presupuestal. Corregido en `core-028-fix-cost-settings-ambiguous.sql`.

### Tests añadidos

- `api/cost_modules_test.ts` — CRUD RPC gastos, flujo almacén completo, control presupuestal, IAM permisos
- `scripts/crud-smoke-test.sh` — smoke HTTP de todos los endpoints nuevos

### Resultados

- `deno test cost_modules_test.ts` — 5/5 OK
- `./scripts/crud-smoke-test.sh` — todos los checks OK (gastos CRUD, IVA, cost-settings, almacén, transferencias, cost-control, IAM)
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-eeec3b5c-f789-43e8-a353-0755e4706377?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-eeec3b5c-f789-43e8-a353-0755e4706377&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-04 04:29:02 +00:00
Cursor Agent
130decd48c
test: validar endpoints y RPCs de gastos, almacén e IAM
- Añade cost_modules_test.ts con CRUD de gastos, almacén, control presupuestal e IAM
- Amplía crud-smoke-test.sh para cubrir todos los endpoints HTTP nuevos
- Corrige migración IAM: grants de core.rpc_* movidos a changeset core-027
- Corrige ambigüedad PL/pgSQL en _cost_settings que rompía fn_expense_create

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-04 04:17:47 +00:00
Cursor Agent
b8e97b5137
fix(api): corregir tipos en iam_http y warehouse_http para deno check
Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-04 04:12:43 +00:00
Cursor Agent
ab89727522
feat: gastos, almacén central/obra y control presupuestal
Implementa el módulo integral de costos según el plan acordado:

- Esquema Liquibase: expense_entries, almacén (central + obra), IVA,
  tenant_cost_settings, permisos IAM y RLS
- Funciones RPC core: gastos, almacén, control presupuestal, sync nómina,
  cierre de almacén al concluir proyecto
- API HTTP: expenses_http, warehouse_http, cost_control_http, iam_http
- Middleware requirePermission con matriz IAM
- UI web-panel: /gastos, /almacen, /control-presupuesto, /usuarios
- Componentes BudgetItemPicker, CostSemaphore y semáforos CSS
- Smoke tests extendidos para gastos/almacén/control

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-04 04:04:17 +00:00