Se descargan correctamente los docx del plan y de la asignatura
This commit is contained in:
@@ -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,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user