Initial commit - Horux Despachos NL

This commit is contained in:
2026-05-03 16:47:53 -06:00
commit b00b677c54
647 changed files with 133843 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
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<CarteraRow[]> {
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<CarteraRow[]> {
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<CarteraRow | null> {
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<CarteraRow> {
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<CarteraRow> {
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<CarteraRow | null> {
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<boolean> {
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<void> {
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<void> {
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<string[]> {
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<Array<{ auxiliarUserId: string }>> {
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<void> {
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<void> {
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<string[]> {
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<Array<{ userId: string; nombre: string; email: string }>> {
// 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,
}));
}