# Sesión: Fix — Crear cliente desde `/clientes` registra como despacho **Fecha:** 2026-05-04 **Bug:** El admin global creaba tenants "legacy" (sin `dbMode`, `verticalProfile`, `trialEndsAt`) desde `/clientes`. Ahora se registran como despachos completos. --- ## 1. Problema ### Síntoma Cuando el administrador global creaba un nuevo cliente desde `/clientes`, el tenant se creaba sin los campos de despacho: - `dbMode` = `null` (debería ser `MANAGED`) - `verticalProfile` = `null` (debería ser `CONTABLE`/`JURIDICO`/`ARQUITECTURA`) - `trialEndsAt` = `null` (debería ser +30 días para plan trial) - `codigoPostal` no se seteaba Esto causaba que el nuevo cliente no funcionara correctamente como despacho (no aparecía en el selector de despachos, no tenía trial, etc.). ### Causa raíz El flujo de **registro público** (`/register-despacho` → `signupDespacho()`) sí seteaba estos campos correctamente. Pero el flujo de **creación por admin** (`/clientes` → `createTenant()`) no lo hacía — solo creaba el tenant con `nombre`, `rfc`, `plan`, `databaseName`. --- ## 2. Cambios realizados ### 2.1 Backend — `tenants.service.ts` **Firma ampliada:** ```ts export async function createTenant(data: { // ... campos existentes ... verticalProfile?: 'CONTABLE' | 'JURIDICO' | 'ARQUITECTURA'; codigoPostal?: string; }) ``` **Creación del tenant ahora setea despacho fields:** ```ts const tenant = await prisma.tenant.create({ data: { nombre: data.nombre, rfc: data.rfc.toUpperCase(), plan, databaseName, dbMode: 'MANAGED', verticalProfile: data.verticalProfile || 'CONTABLE', codigoPostal: data.codigoPostal || undefined, trialEndsAt: isTrial ? new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) : undefined, } }); ``` **Queries `getAllTenants` y `getTenantById` ahora retornan:** - `verticalProfile` - `codigoPostal` ### 2.2 Backend — `tenants.controller.ts` El endpoint `POST /tenants` ahora acepta y pasa: - `verticalProfile` (default: `'CONTABLE'`) - `codigoPostal` (opcional) ### 2.3 Frontend — `clientes/page.tsx` **Nuevo campo en el formulario:** `Tipo de Despacho` - Select con opciones: Contable, Jurídico, Arquitectura - Default: Contable - Se persiste al crear y se carga al editar **C.P. deliberadamente omitido:** El campo `codigoPostal` **no** se incluyó en el formulario. Se obtiene automáticamente de la CSF cuando el dueño del despacho sube su FIEL por primera vez (`fiel.service.ts` → `consultarConstancia()` → `sincronizarDatosFiscales()`). ### 2.4 Frontend — `lib/api/tenants.ts` Tipos actualizados: ```ts export interface Tenant { // ... campos existentes ... verticalProfile?: 'CONTABLE' | 'JURIDICO' | 'ARQUITECTURA' | null; codigoPostal?: string | null; } export interface CreateTenantData { // ... campos existentes ... verticalProfile?: 'CONTABLE' | 'JURIDICO' | 'ARQUITECTURA'; codigoPostal?: string; } ``` --- ## 3. Flujo resultante ``` Admin global crea cliente en /clientes ↓ Tenant creado con: dbMode = 'MANAGED' verticalProfile = (seleccionado por admin, default CONTABLE) trialEndsAt = +30 días (si plan = trial) codigoPostal = undefined (se llenará después) ↓ Dueño del despacho entra a la plataforma ↓ Sube FIEL del despacho (primer contribuyente) ↓ Sistema extrae CSF automáticamente ↓ Tenant actualizado con codigoPostal, calle, estado, regímenes... ``` --- ## 4. Archivos modificados | Archivo | Cambio | |---------|--------| | `apps/api/src/services/tenants.service.ts` | `createTenant` setea `dbMode`, `verticalProfile`, `trialEndsAt`, `codigoPostal`; queries retornan nuevos campos | | `apps/api/src/controllers/tenants.controller.ts` | Acepta `verticalProfile` y `codigoPostal` del body | | `apps/web/app/(dashboard)/clientes/page.tsx` | Formulario con selector de `verticalProfile`; C.P. omitido (viene de CSF) | | `apps/web/lib/api/tenants.ts` | Tipos `Tenant` y `CreateTenantData` ampliados | --- ## 5. Notas - El campo `codigoPostal` del backend es **opcional** y se dejó en la firma por si en el futuro se quiere pasar manualmente (ej. onboarding asistido). - No se requirió migración de base de datos — `codigoPostal` ya existía como columna `String?` en `Tenant`. - `trialEndsAt` se calcula como `Date.now() + 30 días` para plan `trial`, igual que en `signupDespacho()`.