@@ -54,7 +54,7 @@ All endpoints prefixed with `/api/v1`.
## Postgres source contract
Students: `internal/infrastructure/database/postgres.StudentQuery`, a raw SQL query (not a function) joining `matricula.ma_estudiante`/`persona.pe_persona`/`matricula.ma_matricula`/`caja.ca_plan_de_pago`, grouped per student, `payment_plans`/`payment_plans_current_year` built via `JSON_AGG(...) FILTER (...)` split on `periodo_academico_id = 14`. Read directly by `postgres.StudentReader` (`pgx` row scan, no JSON envelope) — one Mongo doc per row, requires numeric `enrollment_id`.
Students: `internal/infrastructure/database/postgres.StudentQuery`, a raw SQL query (not a function) joining `matricula.ma_estudiante`/`persona.pe_persona`/`matricula.ma_matricula`/`caja.ca_plan_de_pago`, grouped per student, `payment_plans`/`payment_plans_current_year` built via `JSON_AGG(...) FILTER (...)` split on `periodo_academico_id = 14`. Read directly by `postgres.StudentReader` (`pgx` row scan, no JSON envelope) — one Mongo doc per row, requires numeric `enrollment_id`. `student_enrollment_photo_url` sale de un `LEFT JOIN LATERAL` sobre `matricula.ma_requisito` (requisito 2961, "Foto tamaño carné"): la URL pública de Drive más reciente entre todas las matrículas del alumno, `null` si no tiene. Suma ~5 s al ciclo porque `ma_requisito` está inflada y no tiene índice por `requisito_documento_id`.
Payment plans: `internal/infrastructure/database/postgres.PaymentPlanQuery`, one document per plan of payment. Read by `postgres.StudentReader (anida los planes de pago)`.