Appearance
Mapa Forense de Integración: Módulos de la Aplicación vs. Tablas de Supabase
Este documento sirve como guía técnica y forense de referencia para la sincronización y persistencia de datos entre el frontend de ITecel y la base de datos PostgreSQL en Supabase.
1. Resumen de Mapeo Módulos ↔ Tablas
| Módulo en Interfaz (Tab) | Tabla(s) Supabase | Clave Primaria (PK) | Claves Foráneas (FK) Críticas |
|---|---|---|---|
Directorio de Viviendas (directory) | housing_units, sections | id (TEXT) | condo_id → condos(id), section_id → sections(id) |
Generación de Cuotas (generateDebts) | debt_templates, generated_debts | id (TEXT) | condo_id → condos(id), unit_id → housing_units(id), template_id → debt_templates(id) |
Cobranzas y Recibos (payments) | payment_records, generated_debts, bank_movements | id (TEXT) | condo_id → condos(id), unit_id → housing_units(id), account_id → bank_accounts(id) |
Egresos y Facturas (expenses) | expenses, suppliers, bank_movements | id (TEXT) | condo_id → condos(id), supplier_id → suppliers(id), account_id → bank_accounts(id) |
Estados de Cuenta (accountStatement) | housing_units, generated_debts, payment_records, statement_dispatches | Lectura combinada + id (TEXT) | Reconciliación estricta por unit_id, condo_id → condos(id) |
Cuentas Bancarias (bankAccounts) | bank_accounts, bank_movements | id (TEXT) | condo_id → condos(id), related_account_id → bank_accounts(id) |
Presupuesto Anual (budget) | budgets | (condo_id, year) | condo_id → condos(id) |
Reportes Financieros (financials) | Agregación de generated_debts, payment_records, expenses, bank_movements | N/A (Lectura) | Consistencia de estados y fechas (issue_date, due_date, date) |
Panel Administrador (adminDashboard) | KPIs calculados desde todas las tablas | N/A (Lectura) | Cartera por cobrar, saldos bancarios, morosidad |
Configuración y Respaldo (settings) | condos + todas las 11 entidades | id (TEXT) | Payload canónico serializeCondoState / deserializeCondoState |
2. Orden Secuencial Obligatorio de Inserción (Topological Sort)
Debido a las restricciones de integridad referencial (FOREIGN KEY ... REFERENCES ... ON DELETE CASCADE), la función supabaseSyncFullCondo ejecuta los UPSERT en 11 pasos estrictos:
condos: Registro raíz del condominio.sections: Secciones/torres históricas (opcionales).housing_units: Viviendas del condominio (Base para adeudos, recibos y movimientos).suppliers: Proveedores de servicios y bienes.bank_accounts: Cuentas bancarias de la copropiedad.debt_templates: Plantillas maestras de emisión de cuotas.generated_debts: Adeudos individuales emitidos por vivienda.- Dependencia:
unit_idDEBE existir previamente enhousing_units. - Dependencia:
template_iddebe existir endebt_templateso sernull.
- Dependencia:
expenses: Egresos registrados (depende desuppliersybank_accounts).payment_records: Recibos de cobro histórico emitidos a copropietarios.bank_movements: Movimientos bancarios (Kardex financiero).- Dependencias:
account_id,payment_record_id,expense_id,debt_id,unit_id.
- Dependencias:
budgets: Presupuestos anuales divididos por categorías de ingresos/egresos ordinarios y extraordinarios.
3. Detalle Forense de la Tabla generated_debts
La tabla generated_debts almacena la cartera y el historial de cobro por vivienda:
sql
CREATE TABLE generated_debts (
id TEXT PRIMARY KEY,
condo_id TEXT NOT NULL REFERENCES condos(id) ON DELETE CASCADE,
unit_id TEXT NOT NULL REFERENCES housing_units(id) ON DELETE CASCADE,
template_id TEXT REFERENCES debt_templates(id) ON DELETE SET NULL,
template_name TEXT DEFAULT '',
unit_number TEXT NOT NULL,
unit_identifier TEXT DEFAULT '',
section_name TEXT DEFAULT '',
concept TEXT NOT NULL,
amount NUMERIC(14,2) NOT NULL DEFAULT 0.00,
discount TEXT DEFAULT '0.00',
discount_days INTEGER DEFAULT 0,
issue_date DATE NOT NULL,
due_date DATE NOT NULL,
budget_category TEXT NOT NULL DEFAULT 'IngOrd - a. ALICUOTAS',
fund_type TEXT NOT NULL DEFAULT 'ordinary',
debt_type TEXT DEFAULT 'fee',
notes TEXT DEFAULT '',
status TEXT NOT NULL DEFAULT 'pending',
paid_amount NUMERIC(14,2) NOT NULL DEFAULT 0.00,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);Reglas de Normalización Aplicadas:
- Reconciliación de
unit_id: Si un adeudo restaurado de backup no tieneunit_idválido o apunta a un ID local obsoleto, el motor de sincronización busca la vivienda porunit_numberounit_identifierencondo.unitsy asigna elunit_idcanónico. - Prevención de Violación FK: Cualquier adeudo huérfano (cuya vivienda fue eliminada del archivo) es filtrado antes del envío por lotes para evitar que aborte la transacción completa. Se registra una advertencia con
console.warn. - Desduplicación de IDs en el Lote: Postgres prohíbe múltiples operaciones sobre la misma clave primaria en una sola sentencia
ON CONFLICT DO UPDATE. Si el array contiene claves repetidas, se desduplican antes del envío. - Sanitización de Fechas: Las fechas
issue_dateydue_dateson forzadas al formato canónicoYYYY-MM-DDsin cadenas vacías ni timestamps inválidos. - Carga en Bloques con Recuperación Fila por Fila (Chunking + Fallback):
- Se sube en bloques de 50 registros (
CHUNK_SIZE = 50). - Si un bloque falla por cualquier razón en Postgres, se activa el fallback fila por fila: salva todos los adeudos válidos y aisla e imprime el error exacto (
message,details,hint,code) de la fila conflictiva.
- Se sube en bloques de 50 registros (
4. Guía de Diagnóstico de Errores Comunes
| Código/Mensaje de Error | Causa Raíz | Solución Implementada |
|---|---|---|
23503: foreign key constraint "generated_debts_unit_id_fkey" | Un adeudo tiene unit_id que no existe en housing_units. | Reconciliación automática por número de vivienda + exclusión preventiva de huérfanos. |
23503: foreign key constraint "generated_debts_template_id_fkey" | Un adeudo referencia una plantilla eliminada o no sincronizada. | template_id se convierte a null si no está en el conjunto de plantillas válidas. |
21000: ON CONFLICT DO UPDATE command cannot affect row a second time | Dos filas en el mismo lote de subida comparten el mismo id. | Desduplicación con sufijo unívoco antes de ejecutar el upsert. |
22007: invalid input syntax for type date: "" | La fecha de emisión o vencimiento viene como cadena vacía "". | Sanitizador sanitizeDate asigna fecha por defecto en formato YYYY-MM-DD. |
23502: null value in column "id" / "concept" violates not-null constraint | Campos obligatorios vacíos o nulos en el objeto JavaScript. | Coerción y fallbacks a cadenas no vacías en generatedDebtToRow. |
5. Matriz de Control de Acceso por Tabla y Rol (Fase 4-A: Row Level Security Real)
Todas las tablas de negocio tienen habilitado Row Level Security (ENABLE ROW LEVEL SECURITY). Las políticas permisivas dev (USING (true)) han sido completamente retiradas.
Identificación de Roles en Base de Datos:
- Superadmin (
is_superadmin()): Usuario autenticado con registro enapp_usersdonderole = 'admin'ycondo_id IS NULL. - Admin de Condominio (
is_condo_admin(condo_id)): Usuario autenticado con registro enapp_usersdonderole = 'admin'ycondo_idcoincide con el condominio. - Comité (
is_condo_committee(condo_id)): Usuario autenticado con registro enapp_usersdonderole = 'committee'ycondo_idcoincide. Solo lectura sobre su condominio. - Residente (
is_condo_resident(condo_id, unit_id)): Usuario autenticado con registro enapp_usersdonderole = 'resident',condo_idcoincide yunit_idcoincide con su vivienda asignada. - Edge Functions / Backend Admin: Conexión vía
SUPABASE_SERVICE_ROLE_KEY(service_role), el cual posee el atributo nativo de PostgresBYPASSRLS.
| Tabla | Superadmin | Admin de Condominio | Miembro del Comité | Residente Asignado | Anónimo / Externo |
|---|---|---|---|---|---|
condos | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT solo de su condo | SELECT solo de su condo | Bloqueado (0 filas) |
sections | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT solo de su condo | SELECT solo de su condo | Bloqueado (0 filas) |
housing_units | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT de todas las unidades de su condo | SELECT únicamente de su unidad asignada (unit_id) | Bloqueado (0 filas) |
bank_accounts | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT solo de su condo | Bloqueado (0 filas) | Bloqueado (0 filas) |
suppliers | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT solo de su condo | Bloqueado (0 filas) | Bloqueado (0 filas) |
debt_templates | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT solo de su condo | Bloqueado (0 filas) | Bloqueado (0 filas) |
generated_debts | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT de todos los adeudos de su condo | SELECT solo de sus adeudos asignados (unit_id) | Bloqueado (0 filas) |
payment_records | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT de todos los pagos de su condo | SELECT solo de sus pagos asignados (unit_id) | Bloqueado (0 filas) |
expenses | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT solo de su condo | Bloqueado (0 filas) | Bloqueado (0 filas) |
bank_movements | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT solo de su condo | Bloqueado (0 filas) | Bloqueado (0 filas) |
yearly_budgets | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT solo de su condo | Bloqueado (0 filas) | Bloqueado (0 filas) |
unit_payments | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT de todos los pagos de su condo | SELECT solo de sus pagos asignados (unit_id) | Bloqueado (0 filas) |
app_users | Total (S, I, U, D) | SELECT / I / U / D de committee y resident de su condo. Ver su propio usuario. | SELECT solo de su propio usuario (auth_user_id / email) | SELECT solo de su propio usuario (auth_user_id / email) | Bloqueado (0 filas) |
user_invitations | Total (S, I, U, D) | Total en su condo (S, I, U, D) | Bloqueado | SELECT solo si email = get_auth_email() | Bloqueado (0 filas) |
statement_dispatches | Total (S, I, U, D) | Total en su condo (S, I, U, D) | SELECT solo de su condo | SELECT solo si coincide su unit_id o email | Bloqueado (0 filas) |
licenses | Total (S, I, U, D) | SELECT solo de la licencia de su propio condo | Bloqueado | Bloqueado | Bloqueado (0 filas) |
central_backups | Total (S, I, U, D) | Bloqueado | Bloqueado | Bloqueado | Bloqueado (0 filas) |
6. Estandarización de Identidad vs. Campos Descriptivos (Mejora M2)
Un principio cardinal derivado de la arquitectura de seguridad y Row Level Security (RLS) en ITECEL ADM es la estricta separación entre Identidad y Campos Descriptivos:
A. Identidad (Seguridad y Control de Acceso)
- Fuente de Verdad:
auth.users(auth.uid(),email) ypublic.app_users(auth_user_id,email,role,condo_id,unit_id). - Propósito: Autenticación criptográfica, evaluación de políticas RLS mediante
get_auth_email()eis_superadmin(), y resolución de contextos de trabajo. - Inmutabilidad Relativa: Modificar el correo de acceso requiere procedimientos de actualización de cuenta en Supabase Auth y sincronización en
app_users.
B. Campos Descriptivos (Informativos e Institucionales)
condos.email/condo.profile.email: Campo puramente descriptivo e institucional. Representa la casilla de correo oficial de contacto o atención del condominio (ej.administracion@condominiolaspalmas.comocontacto@edificiocentral.cl).- Regla Estricta:
condos.emailNUNCA debe ser utilizado para autenticación, validación de permisos RLS o asunciones de identidad de usuario. Un administrador puede gestionar un condominio accediendo con su cuenta personal (admin1@empresa.com) mientras el condominio expone ante copropietarios su correo de soporte institucional (condo@edificio.com). housing_units.email/contacts.email: Representa los canales de notificación para cobranza y avisos de estado de cuenta. La autenticación del residente se vincula únicamente cuando su dirección coincide con un usuario registrado enauth.users/app_usersasociado aunit_id.