Commit Graph

24 Commits

Author SHA1 Message Date
Ernesto Herrera
fa542ddf18 feat(crm): frontend del módulo Tarifario — tarifarios, import Excel y Cotizador
- Cliente API rateSheetsAPI (CRUD, plantilla, import con vista previa, /rate-quote).
- Tarifarios: listado + importar por Excel (plantilla, vista previa, validación) +
  detalle con rutas y activar/vencer.
- Cotizador: calcula opciones de costo por proveedor desde los tarifarios vigentes.
- Nav CRM: Tarifarios y Cotizador.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 09:35:48 -06:00
Ernesto Herrera
8576ec7e37 feat(crm): catálogo Tipos de equipo/contenedor con medidas (Medidas de Equipos)
Agrega el catálogo global 'tipo_equipo' (27 opciones: contenedores marítimos,
ULD aéreos y remolques terrestres) con dimensiones en el campo extra (modo,
largo/ancho/alto, capacidad m³, tara, carga máx., pallets). Base para la sección
FCL del módulo de Cotización (T2026-07-183). Expone extra en el API de catálogos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 09:10:19 -06:00
Ernesto Herrera
de8557dec2 feat(crm): formularios Clientes/Prospectos y Proveedores por catálogo — T2026-07-081/082
Frontend de los catálogos de referencia en BD:
- Store crm-catalogs + cliente API; selects poblados desde /v1/crm/catalogs
  (fiscal: régimen, uso CFDI, forma/método de pago SAT, moneda ISO; comercial;
  país/estado/tipo de domicilio/área). Muestran la descripción, no la clave.
- "Otro → especificar" (medio de contacto y clasificación), observaciones
  generales, y datos de auditoría (ID, fechas, usuarios) en la vista de edición.
- Botón "Editar" explícito en los listados.
- Pantalla de administración de catálogos (insertar/editar/borrar): globales
  solo activar/desactivar; los de la empresa con alta/edición/borrado.
- addresses.country ampliado a 3 (país ISO alfa-3) + migración con down().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 10:31:59 -06:00
Ernesto Herrera
a2a474f8c8 chore(deploy): heap de Node en build + runtime sin red + nginx buffers/http2
Cambios de infra ya aplicados en testing: NODE_OPTIONS max-old-space-size para
evitar OOM en vite build; runtime del frontend sin apk/npm (busybox/node directo);
nginx con proxy_buffers grandes (evita 502 por header del JWT) y http2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 10:31:59 -06:00
Ernesto Herrera
71be225b33 fix(crm): guardar permisos de rol y recargar roles/usuarios tras refresh
- role-permissions.assign enviaba permission_id en el body pero el backend lo
  espera como query param → 422 silencioso; el permiso no se guardaba. Ahora va
  en la query y se confirma con un toast al marcar cada permiso (auto-guardado).
- Roles y Usuarios recargaban solo con el evento companyChanged, que no dispara
  en la hidratación inicial; por eso tras refrescar no aparecían hasta re-elegir
  la compañía. Se cambia a un $effect que reacciona a la compañía activa.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 06:43:35 -06:00
Ernesto Herrera
0cffd351df fix(crm): alta de usuarios carga compañías del tenant en vez de slug a mano
La organización destino salía como texto libre (o fallaba al listar todos los
tenants del Hub con un token KC caduco). Ahora se carga desde /v1/auth/my-companies
(mismo origen que el switcher) y se elige la compañía; el tenant_slug se resuelve
de la compañía seleccionada.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 17:03:36 -06:00
Ernesto Herrera
a14acf59e5 chore(crm): ocultar pestaña Workspace del sidebar
Las altas de organizaciones/usuarios se gestionan desde Compañías, Usuarios y
Roles y permisos; se retira la entrada Workspace (y su icono Building2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:56:59 -06:00
Ernesto Herrera
0d8c4ddf62 fix(crm): catálogo de permisos no cargaba por redirect 307 (slash final)
permissionsAPI.list pegaba a /v1/core/permissions/ (con slash); la ruta backend
es @router.get("") sin slash, así que FastAPI devolvía 307 y el cliente no lo
seguía → "No se pudieron cargar roles/permisos". Se quitan los slash finales de
list/getById/create/update/delete/getModules/getActions para que matcheen exacto.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:51:18 -06:00
Ernesto Herrera
de9e35c501 feat(core): botón "dar de alta usuario" (invitación) en Usuarios + fix hub_admin
- Frontend Usuarios: formulario para dar de alta (email + rol) → invitación; muestra
  el enlace copiable por si el correo no llega.
- Backend invites: resuelve tenant_slug desde la compañía cuando el token no lo trae
  (hub_admin) y usa el token KC de la sesión (valkey) para crear el invite en el Hub.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:12:11 -06:00
Ernesto Herrera
cd3f8c62a4 feat(core): gestión de Usuarios y Roles/permisos (pantallas)
- Roles y permisos: crear/eliminar roles por compañía y editar sus permisos
  (checkboxes agrupados por módulo) vía rolesAPI + rolePermissionsAPI + permissionsAPI.
- Usuarios: lista usuarios de la compañía activa y permite asignar/quitar roles
  vía usersAPI + userRolesAPI. Reusa los clientes API ya existentes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 13:31:11 -06:00
Ernesto Herrera
387b3c0e78 feat(core,crm): auto-sync de tenants Workspace→CRM + mostrar tenant de la compañía activa
- assignable-tenants: para hub_admin, sincroniza automáticamente los tenants del
  Workspace (Hub GET /hub/tenants) a core.tenants con su mismo ID, y los devuelve.
  Así los tenants creados en el Workspace aparecen solos en el CRM para asignarles
  compañías (best-effort con el token KC de la sesión). CRM→Workspace ya lo hace
  Organizaciones (POST /hub/tenants).
- my-companies devuelve tenant_name/tenant_slug (join core.tenants).
- Switcher muestra el tenant de la compañía activa (antes "Sin tenant asignado").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 13:20:50 -06:00
Ernesto Herrera
63ad2e2ecd feat(core,crm): gestión de compañías en el CRM + soporte hub_admin sin tenant
Las compañías (a76.company) se gestionan en el CRM, ligadas a un tenant del
Workspace. Cambios:

- my-companies: por MEMBRESÍA (user_tenants ∪ user_company_roles), así el hub_admin
  sin tenant en el token ve las compañías que creó/se le asignaron; para usuarios
  con tenant, autocrea una por defecto en el primer acceso.
- POST /auth/companies: da de alta una compañía bajo un tenant + asigna al usuario.
  GET /auth/assignable-tenants: tenants elegibles (hub_admin: todos; usuario: el suyo).
- security.validate_access_to_resource: si el token no trae tenant (hub_admin),
  resuelve el tenant desde la compañía activa (a76.company.tenant_id) → puede operar
  por compañía seleccionada.
- set-active: fija sso_tenant_id/pub con el tenant de la compañía (override).
- Pantalla "Compañías" (nav) para crear/listar/seleccionar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 11:44:36 -06:00
Ernesto Herrera
b76d42be83 refactor(auth): login 100% vía Workspace/Hub — sin comunicación directa a Keycloak
Patrón SIWEB: el CRM ya NO habla directo a Keycloak. Se elimina el flujo OIDC
paralelo (que causaba el bucle e "issuer mismatch"):

- workspace-auth.ts: se quitan builders de URL de KC (authorization/login/logout)
  y getPublicKeycloakBaseUrl/Realm/ClientId. getWorkspaceLoginUrl ya no lleva
  return_to a /login?sso_verified (evita el rebote sin sesión = bucle).
- /login: sso_verified=1 → /dashboard; sin sesión → App Launcher del Workspace
  (relay). Se eliminan redirectToKeycloakAuthorization/Login.
- /auth/callback: obsoleto — ya no intercambia code con KC; redirige a /dashboard.
- /logout y /auth/post-logout: limpian sesión local y vuelven al Workspace (el
  logout completo del Hub/KC se hace desde el Workspace).
- /join: usa redirectToWorkspaceLogin en vez de KC.
- lib/auth.ts: initAuth ya no inicializa keycloak-js en el browser; getToken y
  refreshAccessToken operan por cookie + silent-refresh (backend → Hub).

Login = solo App Launcher del Workspace (relay → /auth/sso → Hub /sso-exchange).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:01:10 -06:00
Ernesto Herrera
5d1c65f236 feat(auth): sesión local del CRM (patrón SIWEB) para eliminar el bucle de login
Desacopla la sesión de la app del access token de Keycloak (60s). Tras el SSO/
refresh, el backend emite un JWT de sesión local (HS256) con vida por inactividad
(SESSION_IDLE_MINUTES, cap SESSION_MAX_HOURS) y guarda los tokens KC en valkey.
La app usa esa sesión local como bearer (cookie access_token); el token KC vive en
kc_access_token solo para llamadas directas al Hub. Así el refresh KC contra el Hub
solo se intenta al expirar la sesión local, no cada ~60s → se elimina el bucle
causado por el bug "Token is not active" del relay.

Se RESPETA la revocación central de Keycloak: si el Hub rechaza el refresh, la
sesión termina (401). Sin re-emisión de fallback → sin bypass de revocación.

Todo detrás de SESSION_STORE_ENABLED (default False = comportamiento idéntico).

Backend: core/local_session.py, core/session_store.py (valkey), verify_token acepta
la sesión local, refresh la emite/actualiza; DTOs con session_token/session_id;
test unitario de local_session.
Frontend: access_token=sesión local, kc_access_token para el Hub; sso/refresh/
silent-refresh/switch-tenant y pantallas de workspace cableadas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 16:50:35 -06:00
Ernesto Herrera
a613a7a6aa feat(crm,workspace): alta de organizaciones y usuarios vía Hub
Nueva sección "Workspace" (Organizaciones + Usuarios) que orquesta el Hub
reenviando el token del usuario autenticado. Sin secretos en el CRM ni
bypass: la autorización la impone el Hub (hub_admin para tenants;
hub_admin o admin del tenant para invitaciones).

- Organizaciones: listar (GET /api/v1/hub/tenants) y crear
  (POST /api/v1/hub/tenants → tenant + realm Keycloak).
- Usuarios: invitación de un solo uso (POST /api/v1/hub/invites) con
  enlace copiable si el correo no llega.
- Se descarta /auth/provision-user (PROVISION_SECRET, machine-to-machine)
  en favor del flujo de invitación con token de admin.
- Helpers puros (slug, validación, extracción de errores del Hub) con
  tests unitarios; estado "requiere permisos" ante 403 y fallback a slug
  manual para admin de tenant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 13:19:21 -06:00
Aduanasoft
45f128a551 fix(deploy): apk add wget best-effort en Dockerfile.prod (redes sin CDN de Alpine)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 09:54:57 -06:00
Aduanasoft
e806512a89 feat(ops,fin,crm): frontend de las reglas del PDF (decisiones, cierre, PDF factura, continuidad)
- Embarque: "Cerrar operación" (costos finales, habilita facturar), "Reprogramar
  salida" (Cut Off), y bitácora con puntos de decisión (autorizar/rechazar → hito
  de corrección) + transporte terrestre/recolección en Datos.
- Factura: "Enviar (PDF)" que genera y guarda el PDF, "Ver PDF", y revisión del
  cliente (en revisión / aprueba / con observaciones); estado en_revision_cliente.
- Solicitud: "Registrar contacto" y "Re-cotizar"; Cotización: "Re-cotizar (clonar)";
  Oportunidad: "Convertir a solicitud" (enlaza embudo→RFQ).
- Clientes API ampliados (ops/fin/crm) + catálogos (incoterms/participantes) +
  etiquetas de estado. svelte-check: 0 errores de tipo en archivos nuevos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:08:07 -06:00
Aduanasoft
e724aeae50 feat(fin,ops): frontend Facturas/Cobranza, bitácora de embarque y uploader de documentos
- Facturas: lista, alta y detalle (Conceptos / Pagos-cobranza / Datos) con
  totales+IVA+saldo y acciones Emitir/Enviar/Cancelar
- Embarque: botón "Generar factura", pestaña "Bitácora" (hitos por defecto
  import/export, completar/agregar) y subida real de archivos a MinIO en documentos
- Clientes/Proveedores: subida de archivos en documentos (RelatedManager)
- clientes API fin + ops(eventos) + uploads; sidebar grupo Facturación
- svelte-check: 0 errores de tipo en archivos nuevos

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 07:24:15 -06:00
Aduanasoft
6f200b4505 feat(crm,ops): frontend de Solicitudes, Cotizaciones y Embarques (páginas con pestañas)
- Solicitudes (RFQ): alta + detalle (Requerimientos / Tarifas)
- Cotizaciones: detalle con conceptos (costo/venta/margen), totales, acciones
  Enviar/Aceptar/Rechazar y "Liberar a Operaciones"
- Embarques: alta + detalle (Datos / Documentos Master-House)
- clientes API comercial + ops; sidebar CRM + grupo Operaciones
- svelte-check: 0 errores de tipo en archivos del CRM/OPS

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 18:21:25 -06:00
Aduanasoft
c26e04bcff refactor(crm): catálogos como páginas completas con pestañas (sin modales)
- Clientes/Prospectos y Proveedores: alta y edición en páginas dedicadas
  (/nuevo y /[id]) respetando el shell del dashboard, en vez de modales
- Info segmentada en pestañas: Generales · Comercial · Fiscal · Observaciones
  · Direcciones · Contactos · Documentos
- Componentes reutilizables AccountFields/SupplierFields; RelatedManager
  ahora renderiza por sección
- svelte-check: 0 errores de tipo en archivos del CRM

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:44:21 -06:00
Aduanasoft
79135d9b5a feat(crm): frontend de catálogos (Proveedores, Clientes enriquecido, detalle)
- Clientes/Prospectos: formulario por secciones (generales, comercial, fiscal)
- Proveedores: nueva página con clasificación múltiple y coberturas
- Páginas de detalle cliente/proveedor con gestión de múltiples direcciones,
  contactos y documentos (componente RelatedManager reutilizable)
- Clientes API tipados (suppliers, addresses, documents) + sidebar actualizado
- svelte-check sin errores de tipo en los archivos del CRM

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:19:14 -06:00
Aduanasoft
2ed6247f1e chore: LF en scripts de contenedor (.gitattributes) + rebrand de títulos a CRM
- docker-entrypoint.sh (backend/frontend) y auth-mode.sh normalizados a LF
  (CRLF rompía el shebang dentro de los contenedores Linux)
- .gitattributes fuerza LF en *.sh
- Títulos HTML (app.html / index.html) → "CRM — Aduanasoft"
- .gitignore: docker-compose.override.yml

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:19:12 -06:00
Aduanasoft
af02145332 feat(crm): frontend del CRM (clientes API, navegación, dashboard + Kanban) y rebrand
- Clientes API tipados por entidad en src/lib/api/crm
- Navegación CRM en el sidebar
- Páginas: panel (KPIs + embudo), cuentas, contactos, prospectos, actividades
- Kanban de oportunidades con drag & drop (mueve entre etapas) y siembra de embudo
- Rebrand plantilla → CRM (.env, docker-compose name, package.json, README)
- Fix: CORE_DB_HOST=postgres (coincide con el servicio del compose)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 09:53:56 -06:00
Aduanasoft
c3d0eedc8d chore: baseline plantilla-proyectos como base del CRM 2026-07-14 09:03:52 -06:00