Se descargan correctamente los docx del plan y de la asignatura

This commit is contained in:
2026-03-20 17:31:59 -06:00
parent b986ec343e
commit 1bce226d15
5 changed files with 176 additions and 82 deletions
+10 -52
View File
@@ -7,74 +7,29 @@ import { requireData, throwIfError } from './_helpers'
import type { Tables } from '@/types/supabase'
import { columnParsers } from '@/lib/asignaturaColumnParsers'
const EDGE = {
carbone_io_wrapper: 'carbone-io-wrapper',
} as const
interface GeneratePdfParams {
plan_estudio_id: string
convertTo?: 'pdf'
}
interface GeneratePdfParamsAsignatura {
asignatura_id: string
}
function isPlainRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function toStringValue(value: unknown): string {
if (typeof value === 'string') return value
if (typeof value === 'number' || typeof value === 'boolean')
return String(value)
if (value === null || value === undefined) return ''
try {
return JSON.stringify(value)
} catch {
return String(value)
}
}
function buildAsignaturaReportData(
row: Pick<
Tables<'asignaturas'>,
'datos' | 'contenido_tematico' | 'criterios_de_evaluacion'
>,
): Record<string, string> {
const out: Record<string, string> = {}
const datosRaw = row.datos
if (isPlainRecord(datosRaw)) {
for (const [k, v] of Object.entries(datosRaw)) {
if (v === null || v === undefined) continue
out[k] = toStringValue(v)
}
}
for (const [key, parser] of Object.entries(columnParsers)) {
if (!parser) continue
const current = out[key]
if (typeof current === 'string' && current.trim()) continue
const rawValue = (row as any)?.[key]
const parsed = parser(rawValue)
if (parsed.trim()) out[key] = parsed
}
return out
convertTo?: 'pdf'
}
export async function fetchPlanPdf({
plan_estudio_id,
convertTo,
}: GeneratePdfParams): Promise<Blob> {
return await invokeEdge<Blob>(
EDGE.carbone_io_wrapper,
{
action: 'downloadReport',
plan_estudio_id,
body: { convertTo: 'pdf' },
body: convertTo ? { convertTo } : {},
},
{
headers: {
@@ -87,6 +42,7 @@ export async function fetchPlanPdf({
export async function fetchAsignaturaPdf({
asignatura_id,
convertTo,
}: GeneratePdfParamsAsignatura): Promise<Blob> {
const supabase = supabaseBrowser()
@@ -106,7 +62,10 @@ export async function fetchAsignaturaPdf({
'Asignatura no encontrada',
)
// const reportData = buildAsignaturaReportData(row)
const body: Record<string, unknown> = {
data: row,
}
if (convertTo) body.convertTo = convertTo
return await invokeEdge<Blob>(
EDGE.carbone_io_wrapper,
@@ -114,8 +73,7 @@ export async function fetchAsignaturaPdf({
action: 'downloadReport',
asignatura_id,
body: {
data: row,
convertTo: 'pdf',
...body,
},
},
{
+64
View File
@@ -27,6 +27,55 @@ export class EdgeFunctionError extends Error {
}
}
// Soporta base64 puro o data:...;base64,...
function decodeBase64ToUint8Array(input: string): Uint8Array {
const trimmed = input.trim()
const base64 = trimmed.startsWith('data:')
? trimmed.slice(trimmed.indexOf(',') + 1)
: trimmed
const bin = atob(base64)
const bytes = new Uint8Array(bin.length)
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)
return bytes
}
function stripDataUrlPrefix(input: string): string {
const trimmed = input.trim()
if (!trimmed.startsWith('data:')) return trimmed
const commaIdx = trimmed.indexOf(',')
return commaIdx >= 0 ? trimmed.slice(commaIdx + 1) : trimmed
}
function looksLikeBase64(s: string): boolean {
const t = stripDataUrlPrefix(s).replace(/\s+/g, '').replace(/=+$/g, '')
// base64 típico: solo chars permitidos y longitud razonable
if (t.length < 64) return false
return /^[A-Za-z0-9+/]+$/.test(t)
}
function startsWithZip(bytes: Uint8Array): boolean {
return bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b // "PK"
}
function startsWithPdf(bytes: Uint8Array): boolean {
return (
bytes.length >= 5 &&
bytes[0] === 0x25 &&
bytes[1] === 0x50 &&
bytes[2] === 0x44 &&
bytes[3] === 0x46 &&
bytes[4] === 0x2d
) // "%PDF-"
}
function binaryStringToUint8Array(input: string): Uint8Array {
const bytes = new Uint8Array(input.length)
for (let i = 0; i < input.length; i++) bytes[i] = input.charCodeAt(i) & 0xff
return bytes
}
export async function invokeEdge<TOut>(
functionName: string,
body?:
@@ -111,5 +160,20 @@ export async function invokeEdge<TOut>(
throw new EdgeFunctionError(message, functionName, status, details)
}
if (opts.responseType === 'blob') {
const anyData: unknown = data
if (anyData instanceof Blob) {
return anyData as TOut
}
throw new EdgeFunctionError(
'La Edge Function no devolvió un binario (Blob) válido.',
functionName,
undefined,
{ receivedType: typeof anyData, received: anyData },
)
}
return data as TOut
}