Documentación Técnica

Arquitectura, modelo de datos, seguridad y operación del sistema.

1. Stack

  • Frontend: React 19 + TanStack Start (SSR Worker) + Vite 7 + Tailwind v4 + shadcn/ui.
  • Backend: Supabase (Postgres + Auth + Storage). Lógica server-side vía createServerFn.
  • Runtime SSR: Cloudflare Workers (nodejs_compat).
  • Routing: File-based en src/routes; layout autenticado en _authenticated/ con ssr: false.

2. Modelo de datos (resumen)

Tablas centrales (más de 60 en total):

  • companies, company_members, company_modules, company_member_sites.
  • plans, plan_modules, licenses (serial único, plan, vigencia, límites).
  • sites, floors, locations.
  • device_catalog, assets, asset_ports, asset_port_vlans, vlan_catalog.
  • stock_items, stock_movements, asset_movements, transfer_requests.
  • racks, rack_unit_change_log, port_connections.
  • projects, project_* (deliverables, documents, matrix, engineering, costing).
  • crm_* (accounts, contacts, opportunities, pipelines, activities).
  • audit_log, label_print_log, email_*, alerts, alert_thresholds.
  • computer_equipment (PCs, laptops, servidores; enums computer_kind / computer_status; trigger reutilizado ensure_device_catalog_entry para auto-alta de catálogo).
  • software_licenses (claves, tipo, asientos totales/usados con constraint, vencimientos, vínculo opcional a computer_equipment).

3. Generación automática de licencia

La función assign_plan_to_company(company, plan_code, enabled_by, override)fue actualizada para crear la licencia en la misma transacción.
  1. Valida que el plan exista y esté activo.
  2. Si override=true, desactiva los company_modules previos.
  3. Habilita los plan_modules del plan elegido.
  4. Genera serial: UPPER(plan_code) || '-' || YYYYMMDD || '-' || HEX10.
  5. Inserta en licenses con vigencia now() + 1 year y copia max_users, max_projects, max_devices, storage_quota_mb del plan.
  6. Marca licencias activas anteriores como expired.
  7. Setea companies.current_license_id a la nueva licencia.

4. RLS y roles

  • Roles centralizados en user_roles + enum app_role (super_admin, admin, employee).
  • Función has_role(_user_id, _role) SECURITY DEFINER para evitar recursión.
  • is_company_member(company_id) y is_super_admin() son los predicados base de las policies.
  • Toda tabla de datos de empresa tiene RLS habilitado y políticas scope por company_id.

5. Server functions

Toda lógica server-side se implementa con createServerFn desde @tanstack/react-start. Las funciones autenticadas usan el middleware requireSupabaseAuth que inyecta supabase (cliente con bearer del usuario), userId y claims en el contexto.

Operaciones privilegiadas cargan supabaseAdmin con await import('@/integrations/supabase/client.server') dentro del handler, previa verificación con has_role.

6. Storage

  • Buckets: company-logos, catalog-images, project-documents, prompt-images.
  • Acceso vía signed URLs para documentos privados.
  • Logos y catálogo son públicos con políticas de tamaño y tipo MIME.

7. Observabilidad

  • audit_log: cambios sensibles con actor, tabla, fila y diff.
  • email_send_log + email_dlq_retry_log: trazabilidad de cada correo.
  • alerts + alert_thresholds: motor de reglas configurable por empresa.
  • rack_sync_log: sincronizaciones con sistemas externos.

8. Despliegue

  • SSR en Cloudflare Workers, assets estáticos en CDN.
  • Migraciones SQL versionadas y revisadas antes de ejecutarse en producción.
  • Linter de Supabase ejecutado tras cada migración.

9. Desactivación de módulos por licencia (trigger + cron)

Cuando licenses.status cambia a expired, revoked osuspended, un trigger AFTER UPDATE ejecuta UPDATE company_modules SET enabled = false WHERE company_id = OLD.company_id. Un job programado (pg_cron) corre a diario buscando licencias con expires_at < now() y las pasa a expired, disparando el mismo trigger. Al asignar un nuevo plan con assign_plan_to_company(..., override := true)los módulos se rehabilitan desde plan_modules.

10. PWA de escaneo (QR / código de barras)

  • public/manifest.webmanifest + iconos maskables 192/512; meta tags apple-mobile-web-app-capable y viewport-fit=cover en src/routes/__root.tsx. Sin service worker en esta iteración.
  • Escáner con @zxing/browser + @zxing/library(BrowserMultiFormatReader, hints: QR_CODE, CODE_128, EAN_13). Componente reutilizable src/components/camera-scanner.tsx con torch y feedback háptico.
  • Resolución de códigos en src/lib/scan.ts: URL de activo → id; UUID → lookup directo; fallback por barcodeasset_tag serial.
  • Rutas: /scan (consulta), /scan/transfer (traslados en lote vía inventory_register_transfer), /scan/audit (auditoría física con export CSV). Todas bajo _authenticated.
  • Acceso desde AppLayout: FAB flotante en móvil y entrada de sidebar.