import type { Pool } from 'pg'; import { prisma } from '../config/database.js'; export interface CarteraRow { id: string; supervisorUserId: string | null; auxiliarUserId: string | null; parentId: string | null; nombre: string; descripcion: string | null; createdAt: string; entidadesCount?: number; subcarterasCount?: number; } const BASE_SELECT = ` SELECT c.id, c.supervisor_user_id AS "supervisorUserId", c.auxiliar_user_id AS "auxiliarUserId", c.parent_id AS "parentId", c.nombre, c.descripcion, c.created_at AS "createdAt", (SELECT count(*) FROM cartera_entidades ce WHERE ce.cartera_id = c.id)::int AS "entidadesCount", (SELECT count(*) FROM carteras sc WHERE sc.parent_id = c.id)::int AS "subcarterasCount" FROM carteras c `; /** * List top-level carteras (parent_id IS NULL). * If supervisorUserId is provided, filter by that supervisor. */ export async function listCarteras(pool: Pool, supervisorUserId?: string): Promise { const conditions = ['c.parent_id IS NULL']; const params: unknown[] = []; if (supervisorUserId) { params.push(supervisorUserId); conditions.push(`c.supervisor_user_id = $${params.length}`); } const { rows } = await pool.query( `${BASE_SELECT} WHERE ${conditions.join(' AND ')} ORDER BY c.created_at DESC`, params, ); return rows; } /** * List subcarteras of a parent cartera. */ export async function listSubcarteras(pool: Pool, parentId: string): Promise { const { rows } = await pool.query( `${BASE_SELECT} WHERE c.parent_id = $1 ORDER BY c.nombre`, [parentId], ); return rows; } export async function getCarteraById(pool: Pool, id: string): Promise { const { rows } = await pool.query(`${BASE_SELECT} WHERE c.id = $1`, [id]); return rows[0] ?? null; } export async function createCartera(pool: Pool, data: { supervisorUserId: string; nombre: string; descripcion?: string; }): Promise { const { rows: [row] } = await pool.query(` INSERT INTO carteras (supervisor_user_id, nombre, descripcion) VALUES ($1, $2, $3) RETURNING id `, [data.supervisorUserId, data.nombre, data.descripcion ?? null]); return (await getCarteraById(pool, row.id))!; } /** * Create a subcartera within a parent cartera, assigned to an auxiliar. */ export async function createSubcartera(pool: Pool, data: { parentId: string; auxiliarUserId: string; nombre: string; descripcion?: string; }): Promise { const { rows: [row] } = await pool.query(` INSERT INTO carteras (parent_id, auxiliar_user_id, nombre, descripcion) VALUES ($1, $2, $3, $4) RETURNING id `, [data.parentId, data.auxiliarUserId, data.nombre, data.descripcion ?? null]); return (await getCarteraById(pool, row.id))!; } export async function updateCartera(pool: Pool, id: string, data: { nombre?: string; descripcion?: string; supervisorUserId?: string; }): Promise { const existing = await getCarteraById(pool, id); if (!existing) return null; const sets: string[] = []; const vals: unknown[] = []; let idx = 1; if (data.nombre !== undefined) { sets.push(`nombre = $${idx}`); vals.push(data.nombre); idx++; } if (data.descripcion !== undefined) { sets.push(`descripcion = $${idx}`); vals.push(data.descripcion); idx++; } if (data.supervisorUserId !== undefined) { sets.push(`supervisor_user_id = $${idx}`); vals.push(data.supervisorUserId); idx++; } if (sets.length === 0) return existing; vals.push(id); await pool.query(`UPDATE carteras SET ${sets.join(', ')} WHERE id = $${idx}`, vals); return (await getCarteraById(pool, id))!; } export async function deleteCartera(pool: Pool, id: string): Promise { const { rowCount } = await pool.query('DELETE FROM carteras WHERE id = $1', [id]); return (rowCount ?? 0) > 0; } // Entidades in cartera export async function addEntidadToCartera(pool: Pool, carteraId: string, entidadId: string): Promise { // Si es subcartera, validar que la entidad pertenezca a la cartera padre const cartera = await getCarteraById(pool, carteraId); if (cartera?.parentId) { const { rows } = await pool.query( 'SELECT 1 FROM cartera_entidades WHERE cartera_id = $1 AND entidad_id = $2', [cartera.parentId, entidadId], ); if (rows.length === 0) { throw new Error('La entidad no pertenece a la cartera padre de esta subcartera'); } } await pool.query('INSERT INTO cartera_entidades (cartera_id, entidad_id) VALUES ($1, $2) ON CONFLICT DO NOTHING', [carteraId, entidadId]); } export async function removeEntidadFromCartera(pool: Pool, carteraId: string, entidadId: string): Promise { await pool.query('DELETE FROM cartera_entidades WHERE cartera_id = $1 AND entidad_id = $2', [carteraId, entidadId]); } export async function getCarteraEntidades(pool: Pool, carteraId: string): Promise { const { rows } = await pool.query('SELECT entidad_id AS "entidadId" FROM cartera_entidades WHERE cartera_id = $1', [carteraId]); return rows.map(r => r.entidadId); } // Auxiliares assigned to a supervisor export async function getAuxiliaresDelSupervisor(pool: Pool, supervisorUserId: string): Promise> { const { rows } = await pool.query( 'SELECT auxiliar_user_id AS "auxiliarUserId" FROM auxiliar_supervisores WHERE supervisor_user_id = $1', [supervisorUserId], ); return rows; } // Legacy auxiliares in cartera (backward compat) export async function addAuxiliarToCartera(pool: Pool, carteraId: string, auxiliarUserId: string): Promise { await pool.query('INSERT INTO cartera_auxiliares (cartera_id, auxiliar_user_id) VALUES ($1, $2) ON CONFLICT DO NOTHING', [carteraId, auxiliarUserId]); } export async function removeAuxiliarFromCartera(pool: Pool, carteraId: string, auxiliarUserId: string): Promise { await pool.query('DELETE FROM cartera_auxiliares WHERE cartera_id = $1 AND auxiliar_user_id = $2', [carteraId, auxiliarUserId]); } export async function getCarteraAuxiliares(pool: Pool, carteraId: string): Promise { const { rows } = await pool.query('SELECT auxiliar_user_id AS "auxiliarUserId" FROM cartera_auxiliares WHERE cartera_id = $1', [carteraId]); return rows.map(r => r.auxiliarUserId); } // Supervisors list (for the invite form dropdown) export async function getSupervisores(pool: Pool, tenantId: string): Promise> { // Query tenant_memberships joined with users for supervisor role (rolId=9) const memberships = await prisma.tenantMembership.findMany({ where: { tenantId, rolId: 9, active: true }, include: { user: { select: { id: true, nombre: true, email: true } } }, }); return memberships.map(m => ({ userId: m.user.id, nombre: m.user.nombre, email: m.user.email, })); }