mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 12:43:18 +00:00
docs: contexto del sistema PANELS para agentes
Añade un documento copiable con arquitectura, contratos RPC, módulos de negocio, rutas y reglas de no-romper según el código actual. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
This commit is contained in:
parent
496273b41c
commit
69bcd297f8
1 changed files with 264 additions and 0 deletions
264
docs/contexto-sistema.md
Normal file
264
docs/contexto-sistema.md
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
# PANELS — contexto del sistema (estado actual)
|
||||||
|
|
||||||
|
Documento para pegar en reglas / contexto de un agente. Describe el producto **como está en el código hoy**, no un roadmap.
|
||||||
|
|
||||||
|
Producto: **PANELS** (repo `panels`, paquete raíz `panel-obra`). Escritorio de obra para constructoras (México): padrón, obras, presupuesto, programa de obra, costos, almacén, gafetes, nómina. Multi-tenant. Consola SaaS aparte para operadores de plataforma.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Mapa de repos / carpetas
|
||||||
|
|
||||||
|
| Ruta | Qué es |
|
||||||
|
|------|--------|
|
||||||
|
| `api/` | API Deno 2 + Hono. Punto de entrada `api/main.ts`. Puerto **8000**. Prefijo `/v1`. |
|
||||||
|
| `web-panel/` | Escritorio de obra. Nuxt 3 SPA (`ssr: false`), PrimeVue 4, puerto **3000**. |
|
||||||
|
| `web-saas/` | Consola operadores. Nuxt 3 SPA, mismo stack, puerto **3001**. |
|
||||||
|
| `web/` | Reservada para website pública. **No** es el producto. |
|
||||||
|
| `db/` | Liquibase: `db/platform`, `db/iam`, `db/core`. Provision: `db/provision/`. |
|
||||||
|
| `docs/` | Ops (`coolify.md`), padrón (`padron-v2.md`), este contexto. |
|
||||||
|
| `overlays/compose.local.yml` | Solo local: Postgres + Redis + provision. **No** usar en Coolify. |
|
||||||
|
| `docker-compose.yml` | Producción: `migrate` → `api` → `web-panel` + `web-saas`. |
|
||||||
|
|
||||||
|
Fronts (dev y nginx) proxifican `/v1` a la API. Cookies same-origin. En prod la API **no** lleva FQDN propio.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Stack y runtime
|
||||||
|
|
||||||
|
- **API:** Deno 2.9, Hono 4, `xlsx`, `pdf-lib`, `qrcode`. Sin FFI. Sin Java en el contenedor de tráfico.
|
||||||
|
- **Migraciones:** Liquibase + JRE 21 en imagen `migrate` (un shot por deploy). **No** corren al arrancar la API.
|
||||||
|
- **Fronts:** Nuxt 3.16, Vue 3, PrimeVue 4, PrimeIcons, Chart.js (panel). Build → nginx Alpine.
|
||||||
|
- **Postgres 16** + **Redis 7**. Object storage S3-compatible (**Cloudflare R2**). Fallback disco `/app/data` solo local.
|
||||||
|
- **CI:** GitHub Actions — provision + Liquibase + aislamiento + `deno check`/`deno test` + build de ambos fronts.
|
||||||
|
|
||||||
|
Dev local:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev:api # api
|
||||||
|
npm run dev:web # web-panel :3000
|
||||||
|
npm run dev:saas # web-saas :3001
|
||||||
|
# Docker todo-en-uno:
|
||||||
|
docker compose -f docker-compose.yml -f overlays/compose.local.yml up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Arquitectura de datos (monolito modular)
|
||||||
|
|
||||||
|
Una instancia Postgres **por ambiente**, **dos bases**. Sin FK entre bases ni entre esquemas `iam` y `core`. `tenant_id` es referencia **lógica** validada en aplicación.
|
||||||
|
|
||||||
|
| Base | Esquema | Changelog | Contenido |
|
||||||
|
|------|---------|-----------|-----------|
|
||||||
|
| `panels_platform` | `public` | `db/platform/` | Control plane: `tenants`, `platform_users`, `smtp_settings`. Solo rutas `/v1/saas/*`. |
|
||||||
|
| `panels_product` | `iam` | `db/iam/` | Usuarios/roles/permisos **del tenant** (quien entra a `web-panel`). |
|
||||||
|
| `panels_product` | `core` | `db/core/` | Negocio: empresas, obras, personal, docs, presupuesto, nómina, gafetes, gastos, almacén, programa de obra. |
|
||||||
|
|
||||||
|
**Dos identidades distintas:**
|
||||||
|
|
||||||
|
- `platform_users` = operadores PANELS (`web-saas`). Rol conceptual `platform_admin`.
|
||||||
|
- `iam.users` = usuarios de obra. Roles `tenant_admin` y `user`.
|
||||||
|
|
||||||
|
Roles Postgres: `panels_{platform,iam,core}_{owner,app}`. Runtime usa `_app`. Liquibase/bootstrap usan `_owner`. Login por username usa `DATABASE_URL_IAM_OWNER` a propósito (bypassa RLS).
|
||||||
|
|
||||||
|
Redis ACL: `panels_iam_redis` (sesiones) y `panels_core_redis` (cache).
|
||||||
|
|
||||||
|
RLS en `core` e `iam`: el middleware `withCoreScope` (`api/scope.ts`) fija `app.tenant_id` en la transacción. Sin eso, políticas fail-closed devuelven 0 filas.
|
||||||
|
|
||||||
|
Liquibase: **siempre** pasar `--context-filter`. `context="dev"` = demo (tenant ARCTEC `ARCT2608`). Producción: `--context-filter=!dev`. Sin filtro, Liquibase corre **todo**, incluido demo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Contrato RPC (capa de negocio)
|
||||||
|
|
||||||
|
Lógica de `core` (y parte de `iam`) vive en funciones Postgres `core.fn_*` / `iam.fn_*`. La API **no** debe `db.prepare()` contra tablas de negocio en rutas nuevas; solo `api/rpc.ts` (`callCoreFn` → `SELECT fn($1::jsonb)`).
|
||||||
|
|
||||||
|
Envelope JSON de toda función:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"code": "OK",
|
||||||
|
"layer": "db",
|
||||||
|
"message": "Mensaje específico en español",
|
||||||
|
"context": { "fn": "fn_..." },
|
||||||
|
"data": {},
|
||||||
|
"errors": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Códigos: `OK`, `CREATED`, `VALIDATION`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `CONFLICT`, `INTERNAL`, `NETWORK`.
|
||||||
|
`api/http_errors.ts` mapea a HTTP y añade `status`. `layer` puede ser `db` | `api` | `front`. Mensajes genéricos están prohibidos.
|
||||||
|
|
||||||
|
Convenciones SQL: un parámetro `payload jsonb`, `SECURITY INVOKER`, `SET search_path = core` (o `iam`), `GRANT EXECUTE` al rol `_app` en el mismo changeset. Changesets core RPC: `006` (infra) … `030` (programa de obra).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Auth, sesión, permisos
|
||||||
|
|
||||||
|
- Login `POST /v1/auth/login`. Sesión en Redis IAM, cookie. `COOKIE_SECURE` en HTTPS.
|
||||||
|
- `GET /v1/auth/me`, `POST /v1/auth/logout`, `POST /v1/auth/change-password`.
|
||||||
|
- Alternativa: header `X-API-Key` **+** `X-Tenant-Id` obligatorio (no ve todos los tenants).
|
||||||
|
- `web-panel` rechaza `realm === "platform"` y manda al SaaS.
|
||||||
|
- Primer admin: `api/scripts/bootstrap-admin.ts` + `SEED_PASSWORD` (no seed de hash en Liquibase).
|
||||||
|
|
||||||
|
Permisos (`iam.permissions`):
|
||||||
|
|
||||||
|
| Código | Uso |
|
||||||
|
|--------|-----|
|
||||||
|
| `manage_workers` | Padrón / expediente |
|
||||||
|
| `manage_projects` | Obras |
|
||||||
|
| `manage_companies` | Empresas |
|
||||||
|
| `manage_budget` | Presupuesto |
|
||||||
|
| `manage_payroll` | Nómina / préstamos |
|
||||||
|
| `manage_documents` | Documentos |
|
||||||
|
| `manage_users` | Usuarios del tenant |
|
||||||
|
| `manage_settings` | Config tenant |
|
||||||
|
| `view_reports` | Tableros |
|
||||||
|
| `view_expenses` / `manage_expenses` | Gastos y control de costos |
|
||||||
|
| `view_warehouse` / `manage_warehouse` / `close_warehouse` | Almacén |
|
||||||
|
| `manage_cost_settings` | Umbrales presupuestales |
|
||||||
|
|
||||||
|
`tenant_admin` tiene todos. `user` de fábrica: workers, documents, reports, view_expenses, view_warehouse. `tenant_admin` no se puede recortar por API.
|
||||||
|
|
||||||
|
Granularidad: gastos/almacén/costos ya chequean permiso; el resto de rutas core a menudo solo `requireCoreAuth`. Evolucionar permisos de forma incremental.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Dominio de negocio (`core`)
|
||||||
|
|
||||||
|
### Empresas y obras
|
||||||
|
|
||||||
|
- `companies`: `kind` `principal` | `sub`, `parent_id`, datos fiscales IMSS/RFC, `tenant_id`.
|
||||||
|
- `projects`: obra. Status `activo` | `pausado` | `concluido` | `cancelado`. Tema de gafete, logos, contrato, SIROC, `%` impuesto nómina, empresa dueña.
|
||||||
|
|
||||||
|
### Padrón (persona maestra)
|
||||||
|
|
||||||
|
Invariantes (`docs/padron-v2.md`):
|
||||||
|
|
||||||
|
1. No se borra persona: `status=baja`.
|
||||||
|
2. Solo asignar/importar a proyectos **activos**.
|
||||||
|
3. Asignación repetida se **reactiva**; al finalizar se guarda `end_date`. Unique `(worker_id, project_id)`.
|
||||||
|
4. `pipeline_status` **no** es editable. Baja es manual; el resto se recalcula.
|
||||||
|
5. CURP, RFC, NSS únicos (`citext` CURP/RFC).
|
||||||
|
6. Expediente obligatorio: foto, INE, CURP, NSS/IMSS, RFC.
|
||||||
|
7. Color de gafete = código exacto de `risk_levels`; Alto/Medio/Bajo es UI.
|
||||||
|
|
||||||
|
Campos clave worker: nombres, `hire_type`, `work_type` `N` (jornal) | `D` (destajo), `daily_wage`, `needs_badge`, `imss_status` (`sin_alta` | `alta` | `baja_imss`), empresa IMSS.
|
||||||
|
|
||||||
|
Validación MX en `api/mx.ts`. Import Excel: template + `POST /v1/workers/import`.
|
||||||
|
|
||||||
|
Documentos cifrados AES-GCM (`DOCS_KEY` = 64 hex). Storage key + `iv` en tablas; bytes en R2. Tipos con `validity_mode` `none` | `freshness` | `expiry`. Mismo patrón para docs de proyecto y empresa.
|
||||||
|
|
||||||
|
### Gafetes
|
||||||
|
|
||||||
|
Temas, jobs PDF, QR/vCard (`VCARD_BASE`). Pipeline “listo para gafete” derivado. Kanban visualiza ese pipeline.
|
||||||
|
|
||||||
|
### Presupuesto
|
||||||
|
|
||||||
|
Capítulos + partidas (`budget_chapters` / `budget_items`). Import/export Excel, preview. Ligado a proyecto.
|
||||||
|
|
||||||
|
### Programa de obra
|
||||||
|
|
||||||
|
Un programa por proyecto. Periodos semanales. Slots de concepto, montos por partida, avance. Import, wizard, recálculo vs costo. Export Excel.
|
||||||
|
|
||||||
|
### Gastos y control de costos
|
||||||
|
|
||||||
|
`expense_entries` + adjuntos, IVA por periodo, `tenant_cost_settings`. Control: summary / items / chapters / deviations vs presupuesto.
|
||||||
|
|
||||||
|
### Almacén
|
||||||
|
|
||||||
|
Materiales, almacén central y por obra, stock, movimientos, transferencias. Abrir/cerrar almacén de obra. Links material ↔ presupuesto.
|
||||||
|
|
||||||
|
### Nómina
|
||||||
|
|
||||||
|
Dos líneas históricas:
|
||||||
|
|
||||||
|
- Semanas (`payroll_weeks`, sheets, lines): asistencia, assemble/pay, CSV, jornal, líneas admin, destajo (unidades, jobs, cortes).
|
||||||
|
- Periodos (`payroll_periods` / `payroll_lines`) y settings.
|
||||||
|
- Préstamos (`loans` / `loan_payments`) + PDFs de recibo.
|
||||||
|
- Sync de costo de nómina hacia control de costos (`025-rpc-payroll-cost-sync`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. API HTTP (resumen)
|
||||||
|
|
||||||
|
Prefijo `/v1`. CORS credenciales; origins locales 3000/3001 + `CORS_ORIGINS`.
|
||||||
|
|
||||||
|
| Área | Rutas |
|
||||||
|
|------|--------|
|
||||||
|
| Salud | `GET /health` — core, platform, redis.iam, redis.core, storage |
|
||||||
|
| Auth | `/auth/login`, `logout`, `me`, `change-password` |
|
||||||
|
| SaaS | `/saas/tenants`, SMTP get/put/test, send-access |
|
||||||
|
| Catálogos / config | `/catalogs`, `/configuracion` |
|
||||||
|
| Empresas | CRUD + documentos |
|
||||||
|
| Proyectos | CRUD + documentos + budget nested |
|
||||||
|
| Workers | list/get/create/patch, validate, pipeline, assign, import, documents, photo |
|
||||||
|
| Gafetes | `/badge-qr`, `/projects/:id/badge-jobs`, `/badge-jobs` |
|
||||||
|
| Nómina | `/attendance`, `/payroll/*`, `/destajo/*`, `/loans` |
|
||||||
|
| Gastos | `/expenses`, `/iva/summary`, `/cost-settings` |
|
||||||
|
| Costos | `/projects/:id/cost-control/{summary,items,chapters,deviations}` |
|
||||||
|
| Almacén | `/warehouses`, stock, movements, entries, exits, transfers, open/close |
|
||||||
|
| Programa | `/projects/:id/work-program` (+ concepts, partidas, vs-cost, import, generate, export, patches de periodos) |
|
||||||
|
| IAM | `/iam/users`, `/iam/permissions`, `/iam/roles/:role/permissions`, `/me/permissions` |
|
||||||
|
|
||||||
|
Handlers: `main.ts` + `payroll_http.ts`, `expenses_http.ts`, `warehouse_http.ts`, `cost_control_http.ts`, `work_program_http.ts`, `iam_http.ts`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Fronts
|
||||||
|
|
||||||
|
### `web-panel` — escritorio
|
||||||
|
|
||||||
|
UI tipo escritorio: layout `default.vue` (cinta, árbol, detalle, statusbar). Estado compartido `useDesktop()`. Tema claro/oscuro (`panel-theme`). Auth global; `must_change_password` → `/cambiar-password`.
|
||||||
|
|
||||||
|
Menú:
|
||||||
|
|
||||||
|
| Ruta | Módulo |
|
||||||
|
|------|--------|
|
||||||
|
| `/` | Inicio |
|
||||||
|
| `/proyectos` | Obras / empresas |
|
||||||
|
| `/presupuesto` | Presupuesto |
|
||||||
|
| `/programa-obra` | Programa de obra |
|
||||||
|
| `/control-presupuesto` | Control de costos |
|
||||||
|
| `/gastos` | Gastos |
|
||||||
|
| `/almacen` | Almacén |
|
||||||
|
| `/padron`, `/padron/:id` | Padrón + ficha |
|
||||||
|
| `/kanban` | Pipeline de personal |
|
||||||
|
| `/gafetes` | Impresión de gafetes |
|
||||||
|
| `/nomina` | Nómina |
|
||||||
|
| `/usuarios` | IAM del tenant |
|
||||||
|
| `/configuracion` | Config (menú perfil) |
|
||||||
|
| `/login` | Login |
|
||||||
|
|
||||||
|
También existe `/obras.vue` en pages (legado / no está en el menú principal).
|
||||||
|
|
||||||
|
Cliente HTTP: `composables/useApi.ts` + envelopes en `api-response.ts`. Proxy Nitro `/v1` → `127.0.0.1:8000`.
|
||||||
|
|
||||||
|
### `web-saas`
|
||||||
|
|
||||||
|
Rutas: `/login`, `/` (tenants), `/smtp`. Misma API. No comparte UI con el escritorio.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Reglas para agentes (no romper)
|
||||||
|
|
||||||
|
1. **No SQLite.** Postgres + Redis + R2.
|
||||||
|
2. **No JOIN/FK** `iam` ↔ `core` ni platform ↔ product. Snapshots desnormalizados (`uploaded_by`, etc.).
|
||||||
|
3. Schema nuevo = changeset Liquibase nuevo. Nunca editar changeset ya aplicado en ambiente compartido.
|
||||||
|
4. Negocio nuevo en `core.fn_*` + `callCoreFn`. Envelope + mensaje en español específico.
|
||||||
|
5. Rutas `core`: `...requireCoreAuth` para RLS.
|
||||||
|
6. Documentos: cifrar; no guardar plaintext en disco/R2.
|
||||||
|
7. Padrón: no DELETE de personas; pipeline derivado; unicidad CURP/RFC/NSS.
|
||||||
|
8. Coolify: solo `docker-compose.yml`. Overlay local no va a prod.
|
||||||
|
9. Secrets: `.env` gitignored. `DOCS_KEY` 64 hex. Fail-fast en production si faltan secretos.
|
||||||
|
10. Fronts: SPA + PrimeVue; proxy `/v1`; no llamar la API cross-origin en prod.
|
||||||
|
11. Tests API: `cd api && deno task check && deno task test`. Front: `npm run build` en `web-panel` / `web-saas`.
|
||||||
|
12. `web/` no es el panel. Producto = `web-panel` + `web-saas` + `api`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Deploy (Coolify)
|
||||||
|
|
||||||
|
Por ambiente: 1 Postgres (2 bases), 1 Redis (2 ACL), 1 bucket R2, 4 contenedores (`migrate`, `api`, `web-panel`, `web-saas`). Red Docker `coolify` para hostnames internos UUID (no container id de 12 chars).
|
||||||
|
|
||||||
|
Health: `GET /v1/health` debe reportar core, platform, redis.iam, redis.core y storage (`backend: "s3"` en prod). Detalle ops: `docs/coolify.md`, `db/README.md`, `db/provision/README.md`.
|
||||||
Loading…
Reference in a new issue