fix: personalización logo/color por contribuyente en vez de tenant

- Agrega getCustomizationContribuyente, uploadLogoContribuyente,
  updateColorContribuyente en contribuyente-facturapi.service.ts
- Agrega controllers per-contribuyente en facturacion.controller.ts
- Agrega rutas GET/POST/PUT /contribuyentes/:id/facturapi/customization|logo|color
- Modifica CustomizationSection para recibir contribuyenteId, usar endpoints
  per-contribuyente, y corrige useState mal aplicado a useEffect
- Backend y frontend buildeados y deployados
This commit is contained in:
Horux Dev
2026-05-22 18:20:09 +00:00
parent 46846200da
commit 5ba31b7291
5 changed files with 138 additions and 16 deletions

View File

@@ -542,3 +542,66 @@ async function ensureOrgLegalForEmit(
const payload = await buildLegalPayload(contrib, chosenTaxSystem, currentLegal);
await putOrgLegal(orgId, payload);
}
// ── Personalización (logo, color) per-contribuyente ──
export async function getCustomizationContribuyente(
pool: Pool,
contribuyenteId: string,
): Promise<{ logoUrl?: string; color?: string } | null> {
const { rows } = await pool.query<{ facturapi_org_id: string }>(
'SELECT facturapi_org_id FROM facturapi_orgs WHERE contribuyente_id = $1 AND active = true',
[contribuyenteId],
);
if (rows.length === 0) return null;
const userClient = getUserClient();
try {
const org = await userClient.organizations.retrieve(rows[0].facturapi_org_id);
return {
logoUrl: org.customization?.has_logo ? (org.logo_url ?? undefined) : undefined,
color: org.customization?.color || undefined,
};
} catch { return null; }
}
export async function uploadLogoContribuyente(
pool: Pool,
contribuyenteId: string,
logoBase64: string,
): Promise<{ success: boolean; message: string }> {
const { rows } = await pool.query<{ facturapi_org_id: string }>(
'SELECT facturapi_org_id FROM facturapi_orgs WHERE contribuyente_id = $1 AND active = true',
[contribuyenteId],
);
if (rows.length === 0) throw new Error('Organización no configurada');
const userClient = getUserClient();
try {
const buffer = Buffer.from(logoBase64, 'base64');
await userClient.organizations.uploadLogo(rows[0].facturapi_org_id, buffer);
return { success: true, message: 'Logo subido correctamente' };
} catch (error: any) {
return { success: false, message: error.message || 'Error al subir logo' };
}
}
export async function updateColorContribuyente(
pool: Pool,
contribuyenteId: string,
color: string,
): Promise<{ success: boolean; message: string }> {
const { rows } = await pool.query<{ facturapi_org_id: string }>(
'SELECT facturapi_org_id FROM facturapi_orgs WHERE contribuyente_id = $1 AND active = true',
[contribuyenteId],
);
if (rows.length === 0) throw new Error('Organización no configurada');
const userClient = getUserClient();
try {
await userClient.organizations.updateCustomization(rows[0].facturapi_org_id, { color });
return { success: true, message: 'Color actualizado correctamente' };
} catch (error: any) {
return { success: false, message: error.message || 'Error al actualizar color' };
}
}