ERP Grupo Tesela — sistema completo (BD, app, BI e integración Holded)#11
Conversation
- Sector: construccion, arquitectura y promocion inmobiliaria (10 usuarios, meta Fase 2) - Holded como motor contable/fiscal integrado via API (no se reemplaza) - Modulos sectoriales: promociones/obras, comercializacion, proyectos, subcontratas, postventa - Decision de base de datos: PostgreSQL recomendado, opciones de soberania del dato (UE/Barcelona)
- Base de datos: PostgreSQL en Supabase Cloud (region UE), MySQL descartado - Holded: se mantiene como motor contable, integrado por API - Nuevo MODELO-DATOS.md: esquema SQL Postgres + diagrama ER (promociones, obra, comercializacion, terceros, documental) con enlaces a Holded y Attio
- Proyecto Supabase creado (erp-grupo-tesela, region UE) con 14 tablas - RLS base aplicado (acceso anonimo bloqueado) - Prueba end-to-end del modelo de datos superada
- Edge Function que lee contactos de Holded y los vuelca en cliente/proveedor - Upsert por holded_id (indice unico parcial) para no duplicar - Usa secreto HOLDED_API_KEY; pendiente de activar - Documentado el proceso de activacion en ESTADO.md
- 19 acciones de Holded activas en Zapier (find_contact, create_invoice, etc.) - Documentadas las dos vias: Zapier (transaccional) + Edge Function (carga masiva)
- Lee las API keys desde el secret HOLDED_SOCIEDADES (JSON {nombre,key}), no del repo
- Crea/actualiza cada sociedad y asocia sus contactos via sociedad_id
- Upsert por holded_id para no duplicar
- Migracion: columna sociedad_id en cliente/proveedor + unique en sociedad.nombre - Documentada la plantilla del secret HOLDED_SOCIEDADES (sin claves)
- Tablas perfil + acceso_promocion vinculadas a Supabase Auth - Trigger de alta automatica de perfil y funciones helper RLS - RLS fino: direccion=todo; obra/comercial=lectura por promocion + escritura en su dominio - Endurecimiento: funciones helper retiradas del API REST publico - Migraciones SQL versionadas en supabase/migrations/
- Bucket privado 'documentos' con RLS por promocion (ruta <promocion_id>/...) - Funcion promocion_de_path para aplicar permisos a los archivos - Vista v_rentabilidad_promocion (ingresos vs coste real) con security_invoker
- v_comercializacion_promocion: unidades por estado, % colocado, importe vendido - security_invoker para respetar RLS
- Pantalla del panel con KPIs, comercializacion, avance de obra y control presupuestario - Datos de la promocion demo; base para el frontend real del ERP
- Login, Dashboard, Listado de promociones, Panel comercial y Ficha de vivienda - Mismo diseno previsto para Figma, con datos de la promocion demo
- Tabla factura_pendiente + trigger encolar_factura al escriturar compraventa - Backfill de compraventas ya escrituradas; RLS por promocion - Lista para que Make/Zapier emita las facturas en Holded (create_invoice)
- v_tesoreria_promocion: contratado en hitos, cobrado y pendiente de cobro - README que ata toda la documentacion del ERP para el equipo
- Login con Supabase Auth + dashboard de promociones en vivo - Carga v_comercializacion_promocion y v_rentabilidad_promocion (RLS por rol) - Sin framework ni build; clave publishable (publica, seguridad por RLS)
- Edge Function app (verify_jwt=false) sirve la web del ERP - Documentada la URL y el acceso de prueba
…ntos) - Click en una promocion abre su detalle con KPIs y secciones cargadas en vivo - Re-desplegada la Edge Function app (v2) con lista + detalle - Router simple lista<->detalle en app/app.js
- Boton Vender en el detalle: crea contrato_venta (modal con cliente y precio) - Trigger marca la unidad vendida automaticamente al registrar la venta - Cierra el ciclo: venta -> unidad vendida -> factura encolada - Re-desplegada la Edge Function app (v3)
- Vista v_resumen_grupo: KPIs agregados (cartera, vendido, margen, caja) - Banda de resumen en la pantalla de inicio + promociones ordenadas por margen - Re-desplegada la Edge Function app (v4)
- Boton '+ Cert.' por contrato en el detalle (rol direccion/obra) - Modal con numero, importe y fecha -> inserta certificacion - El coste real y el margen se actualizan solos (verificado) - RLS obra_certificacion + Edge Function app v5
- Sincronizacion via API directa de Holded (pg_net) en Supabase - 28 clientes + 19 proveedores reales volcados en la BD - 4 de 5 keys invalidas (rotadas); pendientes las vigentes - Rastro temporal de pg_net eliminado por seguridad
- Navegacion Promociones/Contactos en la topbar - Tablas de clientes y proveedores (incluye los 47 reales de Holded) - Edge Function app v6
- Tabla factura_holded (ventas y compras), RLS solo direccion - 56 facturas reales importadas (30 ventas + 26 compras) via API Holded - Rastro temporal de pg_net eliminado
- Navegacion Facturas con KPIs (ventas/compras/pendiente cobro/pago) y tabla - Lee factura_holded (56 facturas reales); visible solo para Direccion - Edge Function app v7
- Key cifrada en Supabase Vault (RPC get_holded_keys, solo service_role) - sync-holded reescrita: lee del vault, sincroniza contactos + facturas - Cron diario (pg_cron) a las 06:00 que invoca la funcion via pg_net - Indices UNIQUE en holded_id para upsert idempotente - Probada end-to-end: 28 clientes, 19 proveedores, 56 facturas
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 00da5b57ea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| '0 6 * * *', | ||
| $$ select net.http_post( | ||
| url := 'https://jpojckqnhepiuwefyvdr.supabase.co/functions/v1/sync-holded', | ||
| headers := '{"Content-Type":"application/json"}'::jsonb, |
There was a problem hiding this comment.
Authenticate the scheduled Edge Function call
This cron job posts only Content-Type, and the repo does not add a supabase/config.toml entry disabling JWT verification for sync-holded; Supabase documents that verify_jwt is enabled by default and returns 401 when the Authorization header is missing, while pg_net/service calls should authenticate with an apikey/secret pattern. As written, the daily Holded sync will be rejected before index.ts runs unless it was manually deployed outside the repo with different auth settings.
Useful? React with 👍 / 👎.
| @@ -0,0 +1,13 @@ | |||
| # Edge Function `app` — publicación de la web del ERP | |||
There was a problem hiding this comment.
Add the actual app Edge Function entrypoint
The committed supabase/functions/app directory contains only this README (repo-wide search shows no supabase/functions/app/index.ts), so the documented /functions/v1/app Edge Function cannot be built or redeployed from the repository. Please include the function source/entrypoint or change the deployment instructions to the static app/ assets so the published web app is reproducible.
Useful? React with 👍 / 👎.
…mmendations-hzwhi4
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1ce7da0566
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| create or replace function public.get_holded_keys() | ||
| returns text language sql security definer set search_path = public, vault | ||
| as $$ select decrypted_secret from vault.decrypted_secrets where name = 'holded_sociedades' limit 1 $$; |
There was a problem hiding this comment.
Align the Holded secret lookup with setup docs
The committed setup instructions tell operators to create an Edge Function secret named HOLDED_SOCIEDADES (docs/erp-grupo-tesela/ESTADO.md:83), but this RPC reads a database Vault secret named lowercase holded_sociedades. When setup follows the docs, sync-holded gets no keys and returns its 400 error before importing contacts or invoices; either read the Edge Function env var or align the Vault secret name/instructions.
Useful? React with 👍 / 👎.
- P1: anadir migraciones base Fase 0 (01-04) -> el esquema se recrea desde cero
- P1: supabase/config.toml con verify_jwt=false para app y sync-holded (cron)
- P2: anadir supabase/functions/app/index.ts (función reproducible desde el repo)
- P2: escapar (anti-XSS) los datos importados de Holded antes de render (app + edge fn)
- P2: v_comercializacion_promocion devuelve 'estado' (la app ya no muestra '—')
- P2: documentar que las keys van en Vault ('holded_sociedades'), no en Edge secret
|
He abordado los comentarios de la revisión (commit
Nota: el fallo previo de CI ( Generated by Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74527b468e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -0,0 +1,16 @@ | |||
| -- Fase 0 — RLS base: bloquea el acceso anónimo, permite a usuarios autenticados. | |||
There was a problem hiding this comment.
Use unique Supabase migration timestamps
This file name has the same leading timestamp as _01 and the rest of the new migrations. Supabase CLI parses migration versions from only the leading digits before the first underscore (^([0-9]+)_(.*)\.sql$) and stores that version as the primary key in supabase_migrations.schema_migrations, so after _01 records version 20260608, applying this second file fails with a duplicate migration version. Rename these files to unique full timestamps, or combine the ordered steps into one migration, so supabase db reset/db push can apply the schema.
Useful? React with 👍 / 👎.
| # por eso se despliega con verify_jwt = false. La función no expone datos por GET: | ||
| # lee las keys del Vault y solo escribe en las tablas del ERP. | ||
| [functions.sync-holded] | ||
| verify_jwt = false |
There was a problem hiding this comment.
Protect the service-role Holded sync endpoint
The fresh evidence is that this config explicitly disables JWT verification while sync-holded/index.ts accepts every request and immediately creates a Supabase client with SUPABASE_SERVICE_ROLE_KEY. Anyone who can reach /functions/v1/sync-holded can trigger reads against all configured Holded accounts, upsert ERP data, and receive the sociedad/count summary; use a scheduled-call secret/API key check instead of making the service-role sync function public.
Useful? React with 👍 / 👎.
| create table contrato_venta ( | ||
| id uuid primary key default gen_random_uuid(), | ||
| reserva_id uuid references reserva(id), | ||
| unidad_id uuid references unidad(id), |
There was a problem hiding this comment.
Enforce one sale contract per unit
When a commercial double-clicks the sale confirmation or posts two REST inserts for the same assigned unidad_id, both rows satisfy the RLS policy and the trigger only marks the unit as vendida; nothing on this table prevents multiple active contracts for one unit. The rentabilidad/comercialización views then sum both contracts, corrupting revenue and allowing the same unit to be sold to multiple clients, so unidad_id needs an appropriate unique/partial unique constraint or equivalent transactional guard.
Useful? React with 👍 / 👎.
| join public.fase f on f.promocion_id = pr.id | ||
| join public.unidad u on u.fase_id = f.id |
There was a problem hiding this comment.
Keep empty promotions visible in the dashboard
With these inner joins, any promotion that has no phase yet, or has phases but no units, is omitted from v_comercializacion_promocion. The frontend uses this view as the source for the promotions list, so a newly created/assigned promotion in suelo or proyecto can disappear entirely and the user may see “No tienes promociones visibles”; use left joins and zero counts to keep accessible promotions visible before units are loaded.
Useful? React with 👍 / 👎.
| ${ud.map((u) => { | ||
| const comp = esc(u.contrato_venta?.[0]?.cliente?.nombre) || "—"; | ||
| const accion = (puedeVender && u.estado !== "vendida") | ||
| ? `<button class="mini" onclick="registrarVenta('${u.id}',${u.precio_venta || 0},'${esc(u.referencia)}')">Vender</button>` |
There was a problem hiding this comment.
Avoid interpolating unit refs into inline handlers
For a unit reference containing a single quote, esc() emits ', but browsers decode character references in an onclick attribute before compiling the handler, so the decoded quote terminates the JavaScript string and the rest of the reference can execute when a user clicks “Vender”. Use an event listener with data stored outside inline JavaScript, or JavaScript-string escaping such as JSON.stringify, instead of HTML escaping for this handler argument.
Useful? React with 👍 / 👎.
🔍 Revisión — bloqueantes pendientes antes de fusionarResumen de los puntos detectados (incluye los comentarios de Codex aún sin resolver). Marcados por prioridad. 🔴 Mayores (bloqueantes)1. XSS en 2. Faltan las migraciones base (Fase 0). La primera migración versionada en 3. El cron de 4. Falta el entrypoint de la Edge Function 🟡 Menores / inconsistencias5. Estado de promoción vacío. El frontend muestra 6. Desajuste en el nombre del secreto de Holded. La doc ( 🟢 PositivoAlcance funcional muy completo y bien documentado (INDICE/ARQUITECTURA/MODELO-DATOS). Credenciales fuera del repo (Vault). Buen uso de RLS por promoción y Recomendación: Request changes — abordar 1–4 antes de fusionar. |
ERP a medida para Grupo Tesela (construcción · arquitectura · promoción inmobiliaria)
Sistema de gestión completo, en producción, construido sobre Supabase con integración real a Holded.
🗄️ Base de datos y seguridad
📊 BI y automatización
v_rentabilidad_promocion,v_comercializacion_promocion,v_tesoreria_promocion,v_resumen_grupo.🔄 Integración Holded (real)
sync-holded).🖥️ Aplicación web
app(login Supabase Auth).📚 Documentación
docs/erp-grupo-tesela/: INDICE, README, ARQUITECTURA, MODELO-DATOS, ESTADO + prototipos.supabase/migrations/.🤖 Generated with Claude Code
https://claude.ai/code/session_01PKkvWSji6Gz89BK3N15Z5u
Generated by Claude Code