Agrego carpeta del source sql de la Base de Datos

This commit is contained in:
Pablo
2026-08-22 19:10:49 -03:00
parent 11e36bd6c2
commit 88d724fbcf
94 changed files with 7820 additions and 0 deletions
@@ -0,0 +1,79 @@
-- ============================================================================
-- internal.agregado_reservas_mensual
-- ============================================================================
-- PROPÓSITO
-- Calcula, para un mes dado, la concurrencia por actividad: cuántas
-- reservas hubo en total y cuántas se cancelaron. Es la fuente de datos
-- de la hoja ACTIVIDAD_MENSUAL del archivo histórico
-- (Documentation/PoliticaRetencion.md §4.1).
--
-- DOMINIO
-- El sistema descarta los turnos pasados (higiene, 2 meses) pero la
-- concurrencia mensual sí vale conservar agregada. Esta función produce
-- ese agregado ANTES de que el pipeline purgue los turnos del mes
-- (§5.2): el orden importa, porque una vez borrados los turnos las
-- reservas caen por CASCADE y el dato se pierde.
--
-- Sólo aparecen actividades con al menos una reserva en el mes (el JOIN
-- contra reservas las filtra naturalmente). Una actividad sin reservas
-- no aporta fila: su concurrencia fue cero y no hay nada que archivar.
--
-- "totales" incluye las canceladas; "canceladas" es el subconjunto soft-
-- deleted (reservas.cancelada = true). La asistencia neta se deriva
-- restando, pero se archivan los dos números crudos para no perder
-- información.
--
-- PARÁMETROS
-- p_anio_mes DATE. Cualquier día del mes objetivo; se trunca al día 1.
-- El mes se delimita con un rango semiabierto [inicio, inicio+1mes) sobre
-- turnos.fecha.
--
-- AUTORIZACIÓN
-- No valida permiso propio. El schema `internal` no es alcanzable por
-- roles cliente (ver hardening en `database/schema/00_schemas.sql`).
-- Caller legítimo: el pipeline mensual (US-R17). Read-only: no muta
-- estado; la persistencia ocurre cuando la Edge Function appendea al
-- XLSX.
--
-- ERRORES (RAISE EXCEPTION)
-- Ninguno propio.
--
-- RETORNA
-- SETOF filas (anio_mes, actividad_nombre, reservas_totales,
-- reservas_canceladas). Vacío si el mes no tuvo reservas. `anio_mes` se
-- devuelve como TEXTO 'YYYY-MM-DD' (misma razón que en pagos_del_mes: el
-- pipeline lo usa como clave de reemplazo idempotente en el XLSX).
-- ============================================================================
CREATE OR REPLACE FUNCTION internal.agregado_reservas_mensual(p_anio_mes DATE)
RETURNS TABLE (
anio_mes TEXT,
actividad_nombre TEXT,
reservas_totales INTEGER,
reservas_canceladas INTEGER
)
LANGUAGE sql
SECURITY DEFINER
SET search_path = public
SET timezone = 'America/Argentina/Buenos_Aires'
STABLE
AS $$
SELECT
to_char(date_trunc('month', p_anio_mes), 'YYYY-MM-DD') AS anio_mes,
a.nombre AS actividad_nombre,
count(r.id)::int AS reservas_totales,
count(r.id) FILTER (WHERE r.cancelada)::int AS reservas_canceladas
FROM reservas r
JOIN turnos t ON t.id = r.turno_id
JOIN actividades a ON a.id = t.actividad_id
WHERE t.fecha >= date_trunc('month', p_anio_mes)::date
AND t.fecha < (date_trunc('month', p_anio_mes) + INTERVAL '1 month')::date
GROUP BY a.nombre
ORDER BY a.nombre;
$$;
-- Defensa en profundidad: aunque el hardening del schema (00_schemas.sql)
-- ya bloquee USAGE y EXECUTE por default, revocamos explícitamente acá
-- por si en algún futuro alguien afloja las defensas a nivel schema.
REVOKE ALL ON FUNCTION internal.agregado_reservas_mensual(DATE)
FROM PUBLIC, anon, authenticated;
@@ -0,0 +1,95 @@
-- ============================================================================
-- internal.pagos_del_mes
-- ============================================================================
-- PROPÓSITO
-- Devuelve el detalle de los pagos cobrados en un mes dado, ya con la
-- forma de la hoja PAGOS del archivo histórico
-- (Documentation/PoliticaRetencion.md §4.1). Es la fuente que el
-- pipeline mensual (US-R17) appendea al XLSX antes de borrar esos pagos
-- de la base.
--
-- DOMINIO
-- El archivo conserva "una fila por pago realmente cobrado" (§4.1).
-- Por eso:
-- - Se EXCLUYEN los pagos anulados (§4.2): una anulación se manifiesta
-- como ausencia de fila, no como fila marcada. anulado_at IS NULL.
-- - El "plan" sale del snapshot guardado en detalle->>'cuota_nombre'
-- al momento del cobro (lo escribe fc_insertar_pago), con fallback al
-- nombre vigente en tipos_cuota. El snapshot es lo correcto para un
-- archivo: preserva el nombre que tenía el plan cuando se cobró,
-- aunque después se renombre o se borre.
-- - El "metodo" y "registrado_por" se resuelven por join; sus tablas
-- (metodos_pago, usuarios) no se purgan, así que el dato sigue vivo.
--
-- NOTA sobre tipos: hoy todos los pagos son 'cuota_mensual' (los
-- correctivos —devolucion/ajuste— son una decisión cerrada de NO
-- modelar, DominioPagos §10). Si alguna vez se implementan, habrá que
-- revisar qué cuenta como "cobro real" para el archivo; por ahora el
-- único filtro necesario es el de anulados.
--
-- PARÁMETROS
-- p_anio_mes DATE. Cualquier día del mes objetivo; se compara contra
-- pagos.anio_mes_pagado, que ya está truncado al día 1 por constraint.
--
-- AUTORIZACIÓN
-- No valida permiso propio. Schema `internal` no alcanzable por roles
-- cliente (hardening en 00_schemas.sql). Caller legítimo: el pipeline
-- mensual (US-R17), vía conexión PG directa. Read-only.
--
-- ERRORES (RAISE EXCEPTION)
-- Ninguno propio.
--
-- RETORNA
-- SETOF filas con las columnas de la hoja PAGOS, en el orden del §4.1.
-- Vacío si el mes no tuvo pagos cobrados (caso normal del primer año,
-- §4.3).
--
-- Las columnas de fecha se devuelven como TEXTO estable
-- (`fecha_pago` = 'YYYY-MM-DD HH24:MI', `anio_mes_pagado` = 'YYYY-MM-DD').
-- Es deliberado: el pipeline (US-R17) usa `anio_mes_pagado` como clave
-- para "reemplazar las filas de este mes" en el XLSX de forma idempotente,
-- y un texto fijo se compara sin la ambigüedad de las celdas-fecha de
-- Excel. Ordena lexicográfico = cronológico.
-- ============================================================================
CREATE OR REPLACE FUNCTION internal.pagos_del_mes(p_anio_mes DATE)
RETURNS TABLE (
cliente_nombre TEXT,
cliente_dni TEXT,
fecha_pago TEXT,
anio_mes_pagado TEXT,
monto NUMERIC,
metodo TEXT,
plan TEXT,
registrado_por TEXT
)
LANGUAGE sql
SECURITY DEFINER
SET search_path = public
SET timezone = 'America/Argentina/Buenos_Aires'
STABLE
AS $$
SELECT
trim(cl.nombre || ' ' || COALESCE(cl.apellido, '')) AS cliente_nombre,
cl.dni AS cliente_dni,
to_char(p.fecha_pago, 'YYYY-MM-DD HH24:MI') AS fecha_pago,
to_char(p.anio_mes_pagado, 'YYYY-MM-DD') AS anio_mes_pagado,
p.monto_total AS monto,
m.descripcion AS metodo,
COALESCE(p.detalle->>'cuota_nombre', tc.nombre) AS plan,
ru.nombre AS registrado_por
FROM pagos p
JOIN usuarios cl ON cl.id = p.cliente_id
LEFT JOIN metodos_pago m ON m.id = p.metodo_id
LEFT JOIN tipos_cuota tc ON tc.id = p.tipo_cuota_id
LEFT JOIN usuarios ru ON ru.id = p.created_by
WHERE p.anio_mes_pagado = date_trunc('month', p_anio_mes)::date
AND p.anulado_at IS NULL
ORDER BY cliente_nombre, p.fecha_pago;
$$;
-- Defensa en profundidad: aunque el hardening del schema (00_schemas.sql)
-- ya bloquee USAGE y EXECUTE por default, revocamos explícitamente acá
-- por si en algún futuro alguien afloja las defensas a nivel schema.
REVOKE ALL ON FUNCTION internal.pagos_del_mes(DATE)
FROM PUBLIC, anon, authenticated;
@@ -0,0 +1,61 @@
-- ============================================================================
-- internal.pagos_mes_archivado
-- ============================================================================
-- PROPÓSITO
-- Predicado de dominio: ¿los pagos del mes dado ya fueron archivados al
-- XLSX histórico? Centraliza la definición de "mes archivado" para que
-- los guards de pagos (fc_editar_pago, fc_anular_pago — US-R11) y
-- cualquier futuro consumidor consulten un único criterio en vez de
-- reimplementar el EXISTS y arriesgar divergencias.
--
-- DOMINIO
-- Un mes de pagos está archivado sii existe un registro `ok` de archivado
-- para ese mes en internal.archivo_exports (tipo = 'pagos'). Ver
-- Documentation/PoliticaRetencion.md §5.3 y DominioPagos.md §14.
--
-- Sutilezas que el helper encierra:
-- - Sólo cuenta resultado = 'ok'. Un intento `fallo` NO archiva: el
-- mes sigue editable hasta que el archivado tenga éxito.
-- - El target se compara truncado al día 1, igual que se guarda.
-- - Las corridas vacías del primer año escriben `ok` con
-- anio_mes_target NULL (US-R17); como `NULL = <mes>` nunca es TRUE,
-- esas filas no marcan ningún mes como archivado. Correcto: no
-- archivaron nada.
--
-- PARÁMETROS
-- p_anio_mes DATE. El mes a consultar (cualquier día; se trunca).
--
-- AUTORIZACIÓN
-- No valida permiso propio. Schema `internal` no alcanzable por roles
-- cliente (hardening en 00_schemas.sql). Lo invocan fachadas públicas
-- SECURITY DEFINER (fc_editar_pago, fc_anular_pago) ya autorizadas.
--
-- RETORNA
-- boolean. TRUE si el mes está archivado; FALSE si no (incluye p_anio_mes
-- NULL).
--
-- EFECTOS SECUNDARIOS
-- Ninguno (STABLE, sólo lectura).
-- ============================================================================
CREATE OR REPLACE FUNCTION internal.pagos_mes_archivado(p_anio_mes DATE)
RETURNS boolean
LANGUAGE sql
SECURITY DEFINER
SET search_path = public
STABLE
AS $$
SELECT EXISTS (
SELECT 1
FROM internal.archivo_exports
WHERE tipo = 'pagos'
AND resultado = 'ok'
AND anio_mes_target = date_trunc('month', p_anio_mes)::date
);
$$;
-- Defensa en profundidad: aunque el hardening del schema (00_schemas.sql)
-- ya bloquee USAGE y EXECUTE por default, revocamos explícitamente acá
-- por si en algún futuro alguien afloja las defensas a nivel schema.
REVOKE ALL ON FUNCTION internal.pagos_mes_archivado(DATE)
FROM PUBLIC, anon, authenticated;