Catálogos y selects:
- Incoterm como catálogo (nuevo catálogo global 'incoterm') en solicitud y modal
de conversión de oportunidad.
- Moneda como catálogo en Cotizaciones (nuevo/detalle) y Facturación.
- "Tipo de transporte" desde catálogo medio_transporte (antes lista fija).
- Cotizador: origen/destino como selects alineados a las rutas de los tarifarios
activos (endpoint /rate-locations), para que el costeo siempre encuentre ruta.
Bugs de la sesión:
- Direcciones no guardaban: DTO country String(2)→String(3) (ISO alfa-3); se
amplía accounts.country y se normaliza 'MX'→'MEX' (migración).
- Contacto de proveedor mal filtrado: contacts.ts ahora envía supplier_id.
- Selects ilegibles en modo oscuro: regla global select option en app.css.
- Formas de pago SAT a 2 dígitos (01/04/08) en catálogo y valores guardados.
- RelatedManager: editar direcciones/contactos/documentos (antes solo eliminar).
Oportunidades:
- Se quitan etapas Prospecto/Contactado del embudo semilla.
- Fechas separadas won_date/lost_date + motivo de pérdida, con modal al mover a
Ganada/Perdida (migración).
Facturación:
- Folio automático F{AAAA}-{MM}-{NNN} (next_folio entidad F, sin dirección).
- Moneda como catálogo.
UI:
- Giro "otro" habilita campo para especificar (accounts.industry_other, migración).
- Lista de contactos muestra a quién pertenece (cliente/prospecto/proveedor).
- Proveedores: países/puertos/aeropuertos/aduanas por catálogo (select + chips).
Migraciones reversibles (c2d3e4f5a6b7 ya existía; d3e4f5a6b7c8, e4f5a6b7c8d9).
Suite backend en verde (109). svelte-check sin errores nuevos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CRM — Aduanasoft
Sistema CRM construido sobre la plantilla Workspace de Aduanasoft (SvelteKit 5 + FastAPI + PostgreSQL, multi-tenant con Keycloak/Hub). Gestiona cuentas, contactos, prospectos, oportunidades (pipeline Kanban) y actividades comerciales.
Base:
plantillas-proyectos. Este repo conserva el core de la plantilla (auth, tenants, permisos, licencias) y agrega el dominio CRM en backend y frontend.
Stack
| Capa | Tecnología |
|---|---|
| Frontend | SvelteKit 5 (runes) + Tailwind + shadcn-svelte |
| Backend | FastAPI + SQLAlchemy 2.0 + Pydantic v2 |
| Auth | Keycloak (OIDC) vía Workspace Hub |
| Base de datos | PostgreSQL (schema crm) |
| Cache / Queue | Valkey (Redis) + Celery |
| Storage | MinIO (S3-compatible) |
| Contenedores | Docker Compose |
Módulo CRM
Esquema dedicado crm con 7 tablas multi-tenant (tenant_id + company_id, soft delete):
| Entidad | Tabla | Descripción |
|---|---|---|
| Cuentas | crm.accounts |
Empresas cliente/prospecto (importador, IMMEX, agencia aduanal, transportista). Incluye RFC y patente aduanal. |
| Contactos | crm.contacts |
Personas asociadas a una cuenta. |
| Prospectos | crm.leads |
Leads sin calificar; se convierten en cuenta + contacto + oportunidad. |
| Embudos | crm.pipelines |
Embudos de venta por compañía. |
| Etapas | crm.pipeline_stages |
Columnas del Kanban (con probabilidad y etapas terminales ganada/perdida). |
| Oportunidades | crm.opportunities |
Negocios que avanzan por el embudo. |
| Actividades | crm.activities |
Llamadas, reuniones, tareas, correos y notas. |
Endpoints (prefijo /v1/crm)
Todos reciben company_id como query param y validan permisos vía Keycloak/PermissionService.
GET|POST /accounts,GET|PATCH|DELETE /accounts/{id}GET|POST /contacts,GET|PATCH|DELETE /contacts/{id}GET|POST /leads,GET|PATCH|DELETE /leads/{id},POST /leads/{id}/convertGET|POST /pipelines,PATCH|DELETE /pipelines/{id}GET|POST /stages,PATCH|DELETE /stages/{id}GET|POST /opportunities,GET|PATCH|DELETE /opportunities/{id},PATCH /opportunities/{id}/moveGET|POST /activities,GET|PATCH|DELETE /activities/{id},PATCH /activities/{id}/completeGET /metrics— KPIs y embudo por etapa para el dashboard
Permisos
Se registran en el PermissionRegistry al arrancar (25 permisos: crm.access +
crm.{account,contact,lead,opportunity,pipeline,activity}.{view,create,edit,delete}).
Para persistirlos en BD: POST /v1/core/permissions/sync (o el CLI de sincronización).
Inicio rápido (dev)
cp .env.example .env # ajustar CORE_DB_NAME, Keycloak, etc.
./scripts/auth-mode.sh local # login local sin workspace
docker compose up -d
Abre http://localhost:5173 → CRM en el sidebar.
Migraciones
cd backend
alembic upgrade head # crea el schema crm y sus tablas (revisión f1a2b3c4d5e6)
alembic downgrade -1 # revierte el CRM (down() probado)
Pruebas
Backend (servicios CRM):
cd backend
pytest tests/ -q # 24 pruebas de servicios (SQLite en memoria)
En CI/PostgreSQL, define TEST_DATABASE_URL para ejercitar el esquema real y las
políticas RLS (ver docs/ARCHITECTURE.md).
Estructura del CRM
backend/api/v1/modules/crm/
├── router.py # agrega submódulos bajo /crm (y registra permisos)
├── permissions.py # alta de permisos CRM en el registry
├── accounts/ contacts/ leads/ pipelines/ opportunities/ activities/ metrics/
│ └── models.py · dto.py · service.py · routes.py
backend/alembic/versions/f1a2b3c4d5e6_crm_schema.py
frontend/src/
├── lib/api/crm/ # clientes API tipados por entidad
├── lib/components/crm/ # helpers de formato/etiquetas
└── routes/dashboard/crm/
├── +page.svelte # panel (KPIs + embudo)
├── cuentas/ contactos/ prospectos/ actividades/ # CRUD
└── oportunidades/ # Kanban con drag & drop
Convenciones
- Commits: Conventional Commits (
feat:,fix:,refactor:,chore:) - Ramas:
feature/AS-###-desc,fix/AS-###-desc - Código: nombres en inglés, comentarios de negocio en español
- Backend: Pydantic v2, routers por dominio, filtros multi-tenant explícitos
- Frontend: runes (
$state,$derived,$effect,$props), Tailwind utility-first