diff --git a/drizzle.config.ts b/drizzle.config.ts index d57e081..8d1bd36 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from 'drizzle-kit'; if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); export default defineConfig({ - schema: './src/lib/server/db/schema.ts', + schema: './src/lib/server/db/schema/index.ts', dialect: 'postgresql', dbCredentials: { url: process.env.DATABASE_URL }, verbose: true, diff --git a/drizzle/0002_catalogo_en_la_base.sql b/drizzle/0002_catalogo_en_la_base.sql new file mode 100644 index 0000000..09fab32 --- /dev/null +++ b/drizzle/0002_catalogo_en_la_base.sql @@ -0,0 +1,807 @@ +CREATE TABLE "medio" ( + "id" text PRIMARY KEY NOT NULL, + "clase" text NOT NULL, + "clave" text NOT NULL, + "publico" boolean DEFAULT false NOT NULL, + "mime" text NOT NULL, + "bytes" integer NOT NULL, + "duracion_s" integer, + "ancho" integer, + "alto" integer, + "sha256" text NOT NULL, + "alt" text, + "creado_en" timestamp with time zone NOT NULL, + CONSTRAINT "medio_clave_unique" UNIQUE("clave") +); +--> statement-breakpoint +CREATE TABLE "pais" ( + "codigo" char(2) PRIMARY KEY NOT NULL, + "nombre" text NOT NULL, + "gentilicio" text, + "region" text +); +--> statement-breakpoint +CREATE TABLE "persona" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "nombre" text NOT NULL, + "nombre_legal" text, + "ipi" text, + "nacimiento" integer, + "defuncion" integer, + "pais_codigo" char(2), + "retrato_id" text, + "semblanza" text, + "estado" text DEFAULT 'borrador' NOT NULL, + "creado_en" timestamp with time zone NOT NULL, + "actualizado_en" timestamp with time zone, + CONSTRAINT "persona_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "album" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "titulo" text NOT NULL, + "estilo_id" text NOT NULL, + "tipo" text DEFAULT 'album' NOT NULL, + "fecha" text NOT NULL, + "portada_id" text, + "sello" text, + "resumen" text DEFAULT '' NOT NULL, + "destacado" boolean DEFAULT false NOT NULL, + "numero_catalogo" text, + "formatos" text[], + "pais_edicion_codigo" char(2), + "estado" text DEFAULT 'borrador' NOT NULL, + "creado_en" timestamp with time zone NOT NULL, + "actualizado_en" timestamp with time zone, + CONSTRAINT "album_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "album_enlace" ( + "album_id" text NOT NULL, + "plataforma" text NOT NULL, + "url" text NOT NULL, + CONSTRAINT "album_enlace_album_id_plataforma_pk" PRIMARY KEY("album_id","plataforma") +); +--> statement-breakpoint +CREATE TABLE "autoria" ( + "cancion_id" text NOT NULL, + "persona_id" text NOT NULL, + "rol" text NOT NULL, + "porcentaje" numeric(5, 2) NOT NULL, + CONSTRAINT "autoria_cancion_id_persona_id_rol_pk" PRIMARY KEY("cancion_id","persona_id","rol") +); +--> statement-breakpoint +CREATE TABLE "cancion" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "titulo" text NOT NULL, + "version_de_id" text, + "version_nombre" text, + "version_orden" integer DEFAULT 0 NOT NULL, + "estilo_id" text NOT NULL, + "album_id" text, + "numero" integer, + "audio_id" text, + "muestra_id" text, + "duracion_s" integer, + "ano" integer, + "letra" text, + "idioma" text DEFAULT 'es' NOT NULL, + "nota" text, + "inspiracion" text, + "instrumental" boolean DEFAULT false NOT NULL, + "destacada" boolean DEFAULT false NOT NULL, + "portada_id" text, + "ala_venta" boolean DEFAULT false NOT NULL, + "estado" text DEFAULT 'borrador' NOT NULL, + "creado_en" timestamp with time zone NOT NULL, + "actualizado_en" timestamp with time zone, + CONSTRAINT "cancion_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "cancion_enlace" ( + "cancion_id" text NOT NULL, + "plataforma" text NOT NULL, + "url" text NOT NULL, + CONSTRAINT "cancion_enlace_cancion_id_plataforma_pk" PRIMARY KEY("cancion_id","plataforma") +); +--> statement-breakpoint +CREATE TABLE "cancion_etiqueta" ( + "cancion_id" text NOT NULL, + "etiqueta_id" text NOT NULL, + CONSTRAINT "cancion_etiqueta_cancion_id_etiqueta_id_pk" PRIMARY KEY("cancion_id","etiqueta_id") +); +--> statement-breakpoint +CREATE TABLE "cancion_persona" ( + "cancion_id" text NOT NULL, + "persona_id" text NOT NULL, + "rol" text NOT NULL, + "orden" integer DEFAULT 0 NOT NULL, + CONSTRAINT "cancion_persona_cancion_id_persona_id_rol_pk" PRIMARY KEY("cancion_id","persona_id","rol") +); +--> statement-breakpoint +CREATE TABLE "credito_album" ( + "id" text PRIMARY KEY NOT NULL, + "album_id" text NOT NULL, + "persona_id" text, + "nombre" text, + "rol" text NOT NULL, + "orden" integer DEFAULT 0 NOT NULL +); +--> statement-breakpoint +CREATE TABLE "estilo" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "nombre" text NOT NULL, + "resumen" text DEFAULT '' NOT NULL, + "orden" integer DEFAULT 0 NOT NULL, + "principal" boolean DEFAULT false NOT NULL, + "imagen_id" text, + "estado" text DEFAULT 'borrador' NOT NULL, + "creado_en" timestamp with time zone NOT NULL, + "actualizado_en" timestamp with time zone, + CONSTRAINT "estilo_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "etiqueta" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "nombre" text NOT NULL, + CONSTRAINT "etiqueta_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "registro_obra" ( + "cancion_id" text PRIMARY KEY NOT NULL, + "iswc" text, + "isrc" text, + "entidad" text, + "numero" text, + "editorial" text +); +--> statement-breakpoint +CREATE TABLE "traduccion" ( + "cancion_id" text NOT NULL, + "idioma" text NOT NULL, + "texto" text NOT NULL, + CONSTRAINT "traduccion_cancion_id_idioma_pk" PRIMARY KEY("cancion_id","idioma") +); +--> statement-breakpoint +CREATE TABLE "video" ( + "id" text PRIMARY KEY NOT NULL, + "cancion_id" text NOT NULL, + "youtube_id" text NOT NULL, + "titulo" text NOT NULL, + "orden" integer DEFAULT 0 NOT NULL +); +--> statement-breakpoint +CREATE TABLE "acepcion" ( + "id" text PRIMARY KEY NOT NULL, + "termino_id" text NOT NULL, + "numero" integer NOT NULL, + "categoria" text NOT NULL, + "definicion" text NOT NULL, + "ampliacion" text, + "ejemplo" text +); +--> statement-breakpoint +CREATE TABLE "animo" ( + "id" text PRIMARY KEY NOT NULL, + "codigo" text NOT NULL, + "nombre" text NOT NULL, + "gems" text, + "valencia" integer, + "energia" integer, + "descripcion" text, + CONSTRAINT "animo_codigo_unique" UNIQUE("codigo") +); +--> statement-breakpoint +CREATE TABLE "cancion_genero" ( + "cancion_id" text NOT NULL, + "genero_id" text NOT NULL, + CONSTRAINT "cancion_genero_cancion_id_genero_id_pk" PRIMARY KEY("cancion_id","genero_id") +); +--> statement-breakpoint +CREATE TABLE "capitulo" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "titulo" text NOT NULL, + "tipo" text NOT NULL, + "genero_id" text, + "orden" integer DEFAULT 0 NOT NULL, + "resumen" text DEFAULT '' NOT NULL, + "cuerpo" text DEFAULT '' NOT NULL, + "estado" text DEFAULT 'borrador' NOT NULL, + "creado_en" timestamp with time zone NOT NULL, + "actualizado_en" timestamp with time zone, + CONSTRAINT "capitulo_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "capitulo_acepcion" ( + "capitulo_id" text NOT NULL, + "acepcion_id" text NOT NULL, + "automatico" boolean DEFAULT true NOT NULL, + "confirmado" boolean DEFAULT false NOT NULL, + CONSTRAINT "capitulo_acepcion_capitulo_id_acepcion_id_pk" PRIMARY KEY("capitulo_id","acepcion_id") +); +--> statement-breakpoint +CREATE TABLE "compas" ( + "id" text PRIMARY KEY NOT NULL, + "codigo" text NOT NULL, + "nombre" text, + "numerador" integer NOT NULL, + "denominador" integer NOT NULL, + "nota" text, + CONSTRAINT "compas_codigo_unique" UNIQUE("codigo") +); +--> statement-breakpoint +CREATE TABLE "genero" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "nombre" text NOT NULL, + "resumen" text DEFAULT '' NOT NULL, + "origen_lugar" text, + "origen_ano" integer, + "tempo" text, + "rima" text, + "metrica_nota" text, + "compas_nota" text, + "rasgos" text[], + "estado" text DEFAULT 'borrador' NOT NULL, + "origen" text DEFAULT 'manual' NOT NULL, + "modelo_origen" text, + "importado_en" timestamp with time zone, + "revisado_por" text, + "revisado_en" timestamp with time zone, + "creado_en" timestamp with time zone NOT NULL, + "actualizado_en" timestamp with time zone, + CONSTRAINT "genero_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "genero_alias" ( + "id" text PRIMARY KEY NOT NULL, + "genero_id" text NOT NULL, + "nombre" text NOT NULL, + "pais_codigo" char(2), + "nota" text +); +--> statement-breakpoint +CREATE TABLE "genero_compas" ( + "genero_id" text NOT NULL, + "compas_id" text NOT NULL, + "principal" boolean DEFAULT false NOT NULL, + "nota" text, + CONSTRAINT "genero_compas_genero_id_compas_id_pk" PRIMARY KEY("genero_id","compas_id") +); +--> statement-breakpoint +CREATE TABLE "genero_instrumento" ( + "genero_id" text NOT NULL, + "instrumento_id" text NOT NULL, + "importancia" text DEFAULT 'habitual' NOT NULL, + "papel" text, + "orden" integer DEFAULT 0 NOT NULL, + CONSTRAINT "genero_instrumento_genero_id_instrumento_id_pk" PRIMARY KEY("genero_id","instrumento_id") +); +--> statement-breakpoint +CREATE TABLE "genero_metro" ( + "genero_id" text NOT NULL, + "metro_id" text NOT NULL, + "predominante" boolean DEFAULT false NOT NULL, + CONSTRAINT "genero_metro_genero_id_metro_id_pk" PRIMARY KEY("genero_id","metro_id") +); +--> statement-breakpoint +CREATE TABLE "genero_origen" ( + "genero_id" text NOT NULL, + "origen_id" text NOT NULL, + "tipo" text NOT NULL, + "principal" boolean DEFAULT false NOT NULL, + "nota" text, + "orden" integer DEFAULT 0 NOT NULL, + CONSTRAINT "genero_origen_genero_id_origen_id_tipo_pk" PRIMARY KEY("genero_id","origen_id","tipo") +); +--> statement-breakpoint +CREATE TABLE "genero_pais" ( + "genero_id" text NOT NULL, + "pais_codigo" char(2) NOT NULL, + "relacion" text DEFAULT 'adoptado' NOT NULL, + "nota" text, + CONSTRAINT "genero_pais_genero_id_pais_codigo_pk" PRIMARY KEY("genero_id","pais_codigo") +); +--> statement-breakpoint +CREATE TABLE "genero_persona" ( + "genero_id" text NOT NULL, + "persona_id" text NOT NULL, + "papel" text NOT NULL, + "nota" text, + "orden" integer DEFAULT 0 NOT NULL, + CONSTRAINT "genero_persona_genero_id_persona_id_papel_pk" PRIMARY KEY("genero_id","persona_id","papel") +); +--> statement-breakpoint +CREATE TABLE "genero_seccion" ( + "id" text PRIMARY KEY NOT NULL, + "genero_id" text NOT NULL, + "clave" text NOT NULL, + "titulo" text NOT NULL, + "cuerpo" text NOT NULL, + "orden" integer DEFAULT 0 NOT NULL +); +--> statement-breakpoint +CREATE TABLE "genero_tema" ( + "genero_id" text NOT NULL, + "etiqueta_id" text NOT NULL, + "orden" integer DEFAULT 0 NOT NULL, + CONSTRAINT "genero_tema_genero_id_etiqueta_id_pk" PRIMARY KEY("genero_id","etiqueta_id") +); +--> statement-breakpoint +CREATE TABLE "instrumento" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "nombre" text NOT NULL, + "familia" text NOT NULL, + "subfamilia" text, + "pais_codigo" char(2), + "descripcion" text, + "afinacion" text, + "imagen_id" text, + "muestra_id" text, + "estado" text DEFAULT 'borrador' NOT NULL, + "origen" text DEFAULT 'manual' NOT NULL, + "modelo_origen" text, + "importado_en" timestamp with time zone, + "revisado_por" text, + "revisado_en" timestamp with time zone, + CONSTRAINT "instrumento_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "metro" ( + "id" text PRIMARY KEY NOT NULL, + "codigo" text NOT NULL, + "nombre" text NOT NULL, + "silabas" integer, + "termino_id" text, + "nota" text, + CONSTRAINT "metro_codigo_unique" UNIQUE("codigo") +); +--> statement-breakpoint +CREATE TABLE "modelo_ia" ( + "id" text PRIMARY KEY NOT NULL, + "codigo" text NOT NULL, + "familia" text NOT NULL, + "version" text NOT NULL, + "nombre" text NOT NULL, + "vigente" boolean DEFAULT true NOT NULL, + "nota_sintaxis" text, + "esquema" jsonb, + CONSTRAINT "modelo_ia_codigo_unique" UNIQUE("codigo") +); +--> statement-breakpoint +CREATE TABLE "obra_referencia" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "titulo" text NOT NULL, + "ano" integer, + "genero_id" text, + "nota" text, + "enlace" text, + "cita" text, + "estado" text DEFAULT 'borrador' NOT NULL, + "origen" text DEFAULT 'manual' NOT NULL, + "modelo_origen" text, + "importado_en" timestamp with time zone, + "revisado_por" text, + "revisado_en" timestamp with time zone, + CONSTRAINT "obra_referencia_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "obra_referencia_persona" ( + "obra_id" text NOT NULL, + "persona_id" text NOT NULL, + "papel" text NOT NULL, + "orden" integer DEFAULT 0 NOT NULL, + CONSTRAINT "obra_referencia_persona_obra_id_persona_id_papel_pk" PRIMARY KEY("obra_id","persona_id","papel") +); +--> statement-breakpoint +CREATE TABLE "prompt" ( + "id" text PRIMARY KEY NOT NULL, + "modelo_ia_id" text NOT NULL, + "genero_id" text, + "animo_id" text, + "idioma" text DEFAULT 'en' NOT NULL, + "contenido" jsonb NOT NULL, + "notas" text, + "valoracion" integer, + "ejemplo_id" text, + "preferido" boolean DEFAULT false NOT NULL, + "visibilidad" text DEFAULT 'privado' NOT NULL, + "estado" text DEFAULT 'borrador' NOT NULL, + "creado_en" timestamp with time zone NOT NULL, + "actualizado_en" timestamp with time zone +); +--> statement-breakpoint +CREATE TABLE "termino" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "termino" text NOT NULL, + CONSTRAINT "termino_slug_unique" UNIQUE("slug"), + CONSTRAINT "termino_termino_unique" UNIQUE("termino") +); +--> statement-breakpoint +CREATE TABLE "termino_variante" ( + "termino_id" text NOT NULL, + "forma" text NOT NULL, + CONSTRAINT "termino_variante_termino_id_forma_pk" PRIMARY KEY("termino_id","forma") +); +--> statement-breakpoint +CREATE TABLE "documento_legal" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "titulo" text NOT NULL, + "resumen" text DEFAULT '' NOT NULL, + "orden" integer DEFAULT 0 NOT NULL, + "version_vigente_id" text, + CONSTRAINT "documento_legal_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "entrada" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "titulo" text NOT NULL, + "fecha" text NOT NULL, + "resumen" text DEFAULT '' NOT NULL, + "cuerpo" text DEFAULT '' NOT NULL, + "imagen_id" text, + "estado" text DEFAULT 'borrador' NOT NULL, + "creado_en" timestamp with time zone NOT NULL, + "actualizado_en" timestamp with time zone, + CONSTRAINT "entrada_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "entrada_cancion" ( + "entrada_id" text NOT NULL, + "cancion_id" text NOT NULL, + CONSTRAINT "entrada_cancion_entrada_id_cancion_id_pk" PRIMARY KEY("entrada_id","cancion_id") +); +--> statement-breakpoint +CREATE TABLE "entrada_etiqueta" ( + "entrada_id" text NOT NULL, + "etiqueta_id" text NOT NULL, + CONSTRAINT "entrada_etiqueta_entrada_id_etiqueta_id_pk" PRIMARY KEY("entrada_id","etiqueta_id") +); +--> statement-breakpoint +CREATE TABLE "pagina" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "titulo" text NOT NULL, + "descripcion" text DEFAULT '' NOT NULL, + "cuerpo" text DEFAULT '' NOT NULL, + "estado" text DEFAULT 'borrador' NOT NULL, + "creado_en" timestamp with time zone NOT NULL, + "actualizado_en" timestamp with time zone, + CONSTRAINT "pagina_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "redireccion" ( + "desde" text PRIMARY KEY NOT NULL, + "hacia" text NOT NULL, + "permanente" boolean DEFAULT true NOT NULL, + "motivo" text, + "creada_en" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "version_legal" ( + "id" text PRIMARY KEY NOT NULL, + "documento_id" text NOT NULL, + "cuerpo" text NOT NULL, + "vigente_desde" timestamp with time zone NOT NULL, + "resumen_cambios" text +); +--> statement-breakpoint +CREATE TABLE "contador_factura" ( + "serie" text PRIMARY KEY NOT NULL, + "ultimo_numero" integer DEFAULT 0 NOT NULL, + "actualizado_en" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "datos_facturacion" ( + "id" text PRIMARY KEY NOT NULL, + "usuario_id" text NOT NULL, + "tipo" text DEFAULT 'particular' NOT NULL, + "nombre" text NOT NULL, + "nif" text, + "direccion" text, + "cp" text, + "ciudad" text, + "provincia" text, + "pais_codigo" char(2) NOT NULL, + "predeterminada" boolean DEFAULT false NOT NULL, + "creado_en" timestamp with time zone NOT NULL, + "actualizado_en" timestamp with time zone +); +--> statement-breakpoint +CREATE TABLE "descarga" ( + "id" text PRIMARY KEY NOT NULL, + "usuario_id" text NOT NULL, + "cancion_slug" text NOT NULL, + "bajada_en" timestamp with time zone NOT NULL, + "ip" text, + "agente" text +); +--> statement-breakpoint +CREATE TABLE "factura" ( + "id" text PRIMARY KEY NOT NULL, + "pedido_id" text NOT NULL, + "serie" text NOT NULL, + "numero" integer NOT NULL, + "tipo" text DEFAULT 'normal' NOT NULL, + "rectifica_a_id" text, + "fecha" timestamp with time zone NOT NULL, + "base_centimos" integer NOT NULL, + "iva_tipo" integer NOT NULL, + "iva_cuota_centimos" integer NOT NULL, + "total_centimos" integer NOT NULL, + "moneda" text DEFAULT 'eur' NOT NULL, + "datos_congelados" jsonb NOT NULL, + "pais_codigo" char(2) NOT NULL +); +--> statement-breakpoint +CREATE TABLE "reproduccion" ( + "id" text PRIMARY KEY NOT NULL, + "cancion_slug" text NOT NULL, + "usuario_id" text, + "segundos" integer DEFAULT 0 NOT NULL, + "dia" text NOT NULL, + "creado_en" timestamp with time zone NOT NULL +); +--> statement-breakpoint +ALTER TABLE "persona" ADD CONSTRAINT "persona_pais_codigo_pais_codigo_fk" FOREIGN KEY ("pais_codigo") REFERENCES "public"."pais"("codigo") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "persona" ADD CONSTRAINT "persona_retrato_id_medio_id_fk" FOREIGN KEY ("retrato_id") REFERENCES "public"."medio"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "album" ADD CONSTRAINT "album_estilo_id_estilo_id_fk" FOREIGN KEY ("estilo_id") REFERENCES "public"."estilo"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "album" ADD CONSTRAINT "album_portada_id_medio_id_fk" FOREIGN KEY ("portada_id") REFERENCES "public"."medio"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "album" ADD CONSTRAINT "album_pais_edicion_codigo_pais_codigo_fk" FOREIGN KEY ("pais_edicion_codigo") REFERENCES "public"."pais"("codigo") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "album_enlace" ADD CONSTRAINT "album_enlace_album_id_album_id_fk" FOREIGN KEY ("album_id") REFERENCES "public"."album"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "autoria" ADD CONSTRAINT "autoria_cancion_id_cancion_id_fk" FOREIGN KEY ("cancion_id") REFERENCES "public"."cancion"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "autoria" ADD CONSTRAINT "autoria_persona_id_persona_id_fk" FOREIGN KEY ("persona_id") REFERENCES "public"."persona"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion" ADD CONSTRAINT "cancion_estilo_id_estilo_id_fk" FOREIGN KEY ("estilo_id") REFERENCES "public"."estilo"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion" ADD CONSTRAINT "cancion_album_id_album_id_fk" FOREIGN KEY ("album_id") REFERENCES "public"."album"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion" ADD CONSTRAINT "cancion_audio_id_medio_id_fk" FOREIGN KEY ("audio_id") REFERENCES "public"."medio"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion" ADD CONSTRAINT "cancion_muestra_id_medio_id_fk" FOREIGN KEY ("muestra_id") REFERENCES "public"."medio"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion" ADD CONSTRAINT "cancion_portada_id_medio_id_fk" FOREIGN KEY ("portada_id") REFERENCES "public"."medio"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion_enlace" ADD CONSTRAINT "cancion_enlace_cancion_id_cancion_id_fk" FOREIGN KEY ("cancion_id") REFERENCES "public"."cancion"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion_etiqueta" ADD CONSTRAINT "cancion_etiqueta_cancion_id_cancion_id_fk" FOREIGN KEY ("cancion_id") REFERENCES "public"."cancion"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion_etiqueta" ADD CONSTRAINT "cancion_etiqueta_etiqueta_id_etiqueta_id_fk" FOREIGN KEY ("etiqueta_id") REFERENCES "public"."etiqueta"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion_persona" ADD CONSTRAINT "cancion_persona_cancion_id_cancion_id_fk" FOREIGN KEY ("cancion_id") REFERENCES "public"."cancion"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion_persona" ADD CONSTRAINT "cancion_persona_persona_id_persona_id_fk" FOREIGN KEY ("persona_id") REFERENCES "public"."persona"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "credito_album" ADD CONSTRAINT "credito_album_album_id_album_id_fk" FOREIGN KEY ("album_id") REFERENCES "public"."album"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "credito_album" ADD CONSTRAINT "credito_album_persona_id_persona_id_fk" FOREIGN KEY ("persona_id") REFERENCES "public"."persona"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "estilo" ADD CONSTRAINT "estilo_imagen_id_medio_id_fk" FOREIGN KEY ("imagen_id") REFERENCES "public"."medio"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "registro_obra" ADD CONSTRAINT "registro_obra_cancion_id_cancion_id_fk" FOREIGN KEY ("cancion_id") REFERENCES "public"."cancion"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "traduccion" ADD CONSTRAINT "traduccion_cancion_id_cancion_id_fk" FOREIGN KEY ("cancion_id") REFERENCES "public"."cancion"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "video" ADD CONSTRAINT "video_cancion_id_cancion_id_fk" FOREIGN KEY ("cancion_id") REFERENCES "public"."cancion"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "acepcion" ADD CONSTRAINT "acepcion_termino_id_termino_id_fk" FOREIGN KEY ("termino_id") REFERENCES "public"."termino"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion_genero" ADD CONSTRAINT "cancion_genero_cancion_id_cancion_id_fk" FOREIGN KEY ("cancion_id") REFERENCES "public"."cancion"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cancion_genero" ADD CONSTRAINT "cancion_genero_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "capitulo" ADD CONSTRAINT "capitulo_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "capitulo_acepcion" ADD CONSTRAINT "capitulo_acepcion_capitulo_id_capitulo_id_fk" FOREIGN KEY ("capitulo_id") REFERENCES "public"."capitulo"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "capitulo_acepcion" ADD CONSTRAINT "capitulo_acepcion_acepcion_id_acepcion_id_fk" FOREIGN KEY ("acepcion_id") REFERENCES "public"."acepcion"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_alias" ADD CONSTRAINT "genero_alias_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_alias" ADD CONSTRAINT "genero_alias_pais_codigo_pais_codigo_fk" FOREIGN KEY ("pais_codigo") REFERENCES "public"."pais"("codigo") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_compas" ADD CONSTRAINT "genero_compas_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_compas" ADD CONSTRAINT "genero_compas_compas_id_compas_id_fk" FOREIGN KEY ("compas_id") REFERENCES "public"."compas"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_instrumento" ADD CONSTRAINT "genero_instrumento_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_instrumento" ADD CONSTRAINT "genero_instrumento_instrumento_id_instrumento_id_fk" FOREIGN KEY ("instrumento_id") REFERENCES "public"."instrumento"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_metro" ADD CONSTRAINT "genero_metro_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_metro" ADD CONSTRAINT "genero_metro_metro_id_metro_id_fk" FOREIGN KEY ("metro_id") REFERENCES "public"."metro"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_origen" ADD CONSTRAINT "genero_origen_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_origen" ADD CONSTRAINT "genero_origen_origen_id_genero_id_fk" FOREIGN KEY ("origen_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_pais" ADD CONSTRAINT "genero_pais_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_pais" ADD CONSTRAINT "genero_pais_pais_codigo_pais_codigo_fk" FOREIGN KEY ("pais_codigo") REFERENCES "public"."pais"("codigo") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_persona" ADD CONSTRAINT "genero_persona_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_persona" ADD CONSTRAINT "genero_persona_persona_id_persona_id_fk" FOREIGN KEY ("persona_id") REFERENCES "public"."persona"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_seccion" ADD CONSTRAINT "genero_seccion_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_tema" ADD CONSTRAINT "genero_tema_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "genero_tema" ADD CONSTRAINT "genero_tema_etiqueta_id_etiqueta_id_fk" FOREIGN KEY ("etiqueta_id") REFERENCES "public"."etiqueta"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "instrumento" ADD CONSTRAINT "instrumento_pais_codigo_pais_codigo_fk" FOREIGN KEY ("pais_codigo") REFERENCES "public"."pais"("codigo") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "instrumento" ADD CONSTRAINT "instrumento_imagen_id_medio_id_fk" FOREIGN KEY ("imagen_id") REFERENCES "public"."medio"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "instrumento" ADD CONSTRAINT "instrumento_muestra_id_medio_id_fk" FOREIGN KEY ("muestra_id") REFERENCES "public"."medio"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "obra_referencia" ADD CONSTRAINT "obra_referencia_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "obra_referencia_persona" ADD CONSTRAINT "obra_referencia_persona_obra_id_obra_referencia_id_fk" FOREIGN KEY ("obra_id") REFERENCES "public"."obra_referencia"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "obra_referencia_persona" ADD CONSTRAINT "obra_referencia_persona_persona_id_persona_id_fk" FOREIGN KEY ("persona_id") REFERENCES "public"."persona"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "prompt" ADD CONSTRAINT "prompt_modelo_ia_id_modelo_ia_id_fk" FOREIGN KEY ("modelo_ia_id") REFERENCES "public"."modelo_ia"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "prompt" ADD CONSTRAINT "prompt_genero_id_genero_id_fk" FOREIGN KEY ("genero_id") REFERENCES "public"."genero"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "prompt" ADD CONSTRAINT "prompt_animo_id_animo_id_fk" FOREIGN KEY ("animo_id") REFERENCES "public"."animo"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "prompt" ADD CONSTRAINT "prompt_ejemplo_id_medio_id_fk" FOREIGN KEY ("ejemplo_id") REFERENCES "public"."medio"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "termino_variante" ADD CONSTRAINT "termino_variante_termino_id_termino_id_fk" FOREIGN KEY ("termino_id") REFERENCES "public"."termino"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entrada" ADD CONSTRAINT "entrada_imagen_id_medio_id_fk" FOREIGN KEY ("imagen_id") REFERENCES "public"."medio"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entrada_cancion" ADD CONSTRAINT "entrada_cancion_entrada_id_entrada_id_fk" FOREIGN KEY ("entrada_id") REFERENCES "public"."entrada"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entrada_cancion" ADD CONSTRAINT "entrada_cancion_cancion_id_cancion_id_fk" FOREIGN KEY ("cancion_id") REFERENCES "public"."cancion"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entrada_etiqueta" ADD CONSTRAINT "entrada_etiqueta_entrada_id_entrada_id_fk" FOREIGN KEY ("entrada_id") REFERENCES "public"."entrada"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entrada_etiqueta" ADD CONSTRAINT "entrada_etiqueta_etiqueta_id_etiqueta_id_fk" FOREIGN KEY ("etiqueta_id") REFERENCES "public"."etiqueta"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "version_legal" ADD CONSTRAINT "version_legal_documento_id_documento_legal_id_fk" FOREIGN KEY ("documento_id") REFERENCES "public"."documento_legal"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "datos_facturacion" ADD CONSTRAINT "datos_facturacion_usuario_id_usuario_id_fk" FOREIGN KEY ("usuario_id") REFERENCES "public"."usuario"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "datos_facturacion" ADD CONSTRAINT "datos_facturacion_pais_codigo_pais_codigo_fk" FOREIGN KEY ("pais_codigo") REFERENCES "public"."pais"("codigo") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "descarga" ADD CONSTRAINT "descarga_usuario_id_usuario_id_fk" FOREIGN KEY ("usuario_id") REFERENCES "public"."usuario"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "factura" ADD CONSTRAINT "factura_pedido_id_pedido_id_fk" FOREIGN KEY ("pedido_id") REFERENCES "public"."pedido"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "factura" ADD CONSTRAINT "factura_pais_codigo_pais_codigo_fk" FOREIGN KEY ("pais_codigo") REFERENCES "public"."pais"("codigo") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reproduccion" ADD CONSTRAINT "reproduccion_usuario_id_usuario_id_fk" FOREIGN KEY ("usuario_id") REFERENCES "public"."usuario"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "medio_clase" ON "medio" USING btree ("clase");--> statement-breakpoint +CREATE INDEX "medio_sha" ON "medio" USING btree ("sha256");--> statement-breakpoint +CREATE INDEX "persona_nombre" ON "persona" USING btree ("nombre");--> statement-breakpoint +CREATE INDEX "persona_pais" ON "persona" USING btree ("pais_codigo");--> statement-breakpoint +CREATE INDEX "album_estilo" ON "album" USING btree ("estilo_id");--> statement-breakpoint +CREATE INDEX "album_fecha" ON "album" USING btree ("fecha");--> statement-breakpoint +CREATE INDEX "cancion_estilo" ON "cancion" USING btree ("estilo_id");--> statement-breakpoint +CREATE INDEX "cancion_album" ON "cancion" USING btree ("album_id");--> statement-breakpoint +CREATE INDEX "cancion_version_de" ON "cancion" USING btree ("version_de_id");--> statement-breakpoint +CREATE INDEX "cancion_destacada" ON "cancion" USING btree ("destacada");--> statement-breakpoint +CREATE INDEX "credito_album_album" ON "credito_album" USING btree ("album_id");--> statement-breakpoint +CREATE UNIQUE INDEX "estilo_principal_unico" ON "estilo" USING btree ("principal") WHERE "estilo"."principal";--> statement-breakpoint +CREATE INDEX "estilo_orden" ON "estilo" USING btree ("orden");--> statement-breakpoint +CREATE INDEX "video_cancion" ON "video" USING btree ("cancion_id");--> statement-breakpoint +CREATE UNIQUE INDEX "acepcion_numero" ON "acepcion" USING btree ("termino_id","numero");--> statement-breakpoint +CREATE INDEX "capitulo_tipo" ON "capitulo" USING btree ("tipo","orden");--> statement-breakpoint +CREATE INDEX "genero_nombre" ON "genero" USING btree ("nombre");--> statement-breakpoint +CREATE INDEX "genero_origen_ano" ON "genero" USING btree ("origen_ano");--> statement-breakpoint +CREATE INDEX "genero_alias_nombre" ON "genero_alias" USING btree ("nombre");--> statement-breakpoint +CREATE INDEX "genero_origen_origen" ON "genero_origen" USING btree ("origen_id");--> statement-breakpoint +CREATE UNIQUE INDEX "genero_seccion_clave" ON "genero_seccion" USING btree ("genero_id","clave");--> statement-breakpoint +CREATE INDEX "instrumento_familia" ON "instrumento" USING btree ("familia");--> statement-breakpoint +CREATE INDEX "modelo_ia_familia" ON "modelo_ia" USING btree ("familia");--> statement-breakpoint +CREATE INDEX "obra_referencia_genero" ON "obra_referencia" USING btree ("genero_id");--> statement-breakpoint +CREATE INDEX "prompt_genero" ON "prompt" USING btree ("genero_id");--> statement-breakpoint +CREATE INDEX "prompt_modelo" ON "prompt" USING btree ("modelo_ia_id");--> statement-breakpoint +CREATE INDEX "prompt_animo" ON "prompt" USING btree ("animo_id");--> statement-breakpoint +CREATE INDEX "entrada_fecha" ON "entrada" USING btree ("fecha");--> statement-breakpoint +CREATE INDEX "redireccion_hacia" ON "redireccion" USING btree ("hacia");--> statement-breakpoint +CREATE INDEX "version_legal_documento" ON "version_legal" USING btree ("documento_id","vigente_desde");--> statement-breakpoint +CREATE INDEX "datos_facturacion_usuario" ON "datos_facturacion" USING btree ("usuario_id");--> statement-breakpoint +CREATE INDEX "descarga_usuario" ON "descarga" USING btree ("usuario_id");--> statement-breakpoint +CREATE INDEX "descarga_fecha" ON "descarga" USING btree ("bajada_en");--> statement-breakpoint +CREATE UNIQUE INDEX "factura_serie_numero" ON "factura" USING btree ("serie","numero");--> statement-breakpoint +CREATE INDEX "factura_pedido" ON "factura" USING btree ("pedido_id");--> statement-breakpoint +CREATE INDEX "factura_fecha" ON "factura" USING btree ("fecha");--> statement-breakpoint +CREATE INDEX "reproduccion_dia" ON "reproduccion" USING btree ("dia");--> statement-breakpoint +CREATE INDEX "reproduccion_cancion" ON "reproduccion" USING btree ("cancion_slug");--> statement-breakpoint +CREATE INDEX "reproduccion_usuario" ON "reproduccion" USING btree ("usuario_id");--> statement-breakpoint +-- =========================================================================== +-- Lo que Drizzle no sabe expresar, y que es justo lo que impide que el modelo +-- mienta. Añadido a mano; si algún día se regenera esta migración, esto se +-- vuelve a poner. +-- =========================================================================== + +-- --- Las versiones de una canción ----------------------------------------- +-- +-- La clave foránea no se declara en el esquema porque Drizzle no deja +-- referenciar una tabla dentro de su propia definición. +ALTER TABLE "cancion" ADD CONSTRAINT "cancion_version_de_fk" + FOREIGN KEY ("version_de_id") REFERENCES "cancion"("id") ON DELETE cascade; +--> statement-breakpoint + +-- Una canción no es versión de sí misma. +ALTER TABLE "cancion" ADD CONSTRAINT "cancion_no_es_su_propia_version" + CHECK ("version_de_id" IS NULL OR "version_de_id" <> "id"); +--> statement-breakpoint + +-- Y una versión no puede tener versiones: un solo nivel. +-- +-- Se hace con una clave foránea compuesta contra una columna generada, no con +-- un disparador. `es_version` se calcula sola, la pareja (id, es_version) es +-- única, y la referencia exige que el padre tenga `es_version` a falso —lo que +-- garantiza la columna `padre_no_es_version`, fija en falso—. Con eso quedan +-- descartados a la vez el nivel tres y los ciclos, sin escribir procedimientos. +ALTER TABLE "cancion" ADD COLUMN "es_version" boolean + GENERATED ALWAYS AS ("version_de_id" IS NOT NULL) STORED; +--> statement-breakpoint +ALTER TABLE "cancion" ADD COLUMN "padre_no_es_version" boolean + GENERATED ALWAYS AS (CASE WHEN "version_de_id" IS NULL THEN NULL ELSE false END) STORED; +--> statement-breakpoint +ALTER TABLE "cancion" ADD CONSTRAINT "cancion_id_es_version_unica" + UNIQUE ("id", "es_version"); +--> statement-breakpoint +ALTER TABLE "cancion" ADD CONSTRAINT "cancion_version_de_principal" + FOREIGN KEY ("version_de_id", "padre_no_es_version") + REFERENCES "cancion"("id", "es_version"); +--> statement-breakpoint + +-- --- La vista de lo que se lista y se cuenta ------------------------------- +-- +-- Cuatro funciones devuelven listas de canciones y siete rutas las pintan, más +-- los recuentos por estilo y por etiqueta. Si a una se le olvida el filtro, el +-- catálogo dice veinticinco temas donde hay dieciocho y nadie lo nota. Lo +-- normal es consultar esto; pedir versiones es lo que hay que escribir a +-- propósito. +CREATE VIEW "cancion_principal" AS + SELECT * FROM "cancion" WHERE "version_de_id" IS NULL; +--> statement-breakpoint + +-- --- El reparto de autoría suma cien --------------------------------------- +-- +-- Un reparto que no suma cien no es un dato incompleto: es dinero mal +-- repartido. Se comprueba al terminar la transacción y no en cada fila, porque +-- durante una inserción de tres autores los estados intermedios no suman. +CREATE FUNCTION "autoria_suma_cien"() RETURNS trigger AS $$ +DECLARE + total numeric; + obra text; +BEGIN + obra := COALESCE(NEW."cancion_id", OLD."cancion_id"); + SELECT COALESCE(SUM("porcentaje"), 0) INTO total + FROM "autoria" WHERE "cancion_id" = obra; + IF total <> 0 AND total <> 100 THEN + RAISE EXCEPTION 'El reparto de autoría de % suma %, y tiene que sumar 100', obra, total; + END IF; + RETURN NULL; +END; +$$ LANGUAGE plpgsql; +--> statement-breakpoint +CREATE CONSTRAINT TRIGGER "autoria_suma_cien_trg" + AFTER INSERT OR UPDATE OR DELETE ON "autoria" + DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW EXECUTE FUNCTION "autoria_suma_cien"(); +--> statement-breakpoint + +-- --- Un solo estilo principal ---------------------------------------------- +-- +-- Lo genera Drizzle a partir del `uniqueIndex(...).where(...)` del esquema, con +-- su índice parcial: solo entran las filas marcadas, así que la unicidad se +-- aplica entre ellas y las demás no estorban. Antes era un acuerdo escrito en +-- un comentario. Aquí no hay nada que añadir. + +-- --- Una arista principal por género ---------------------------------------- +CREATE UNIQUE INDEX "genero_origen_principal_unico" ON "genero_origen" ("genero_id") + WHERE "principal"; +--> statement-breakpoint + +-- Un género no deriva de sí mismo. +ALTER TABLE "genero_origen" ADD CONSTRAINT "genero_origen_no_reflexivo" + CHECK ("genero_id" <> "origen_id"); +--> statement-breakpoint + +-- --- Sin ciclos en el linaje de géneros ------------------------------------- +-- +-- Aquí sí hace falta un disparador: un grafo admite que A derive de B y B de A, +-- y una consulta recursiva sobre eso no termina. Con un catálogo de cien +-- géneros, comprobarlo al insertar no cuesta nada. +CREATE FUNCTION "genero_sin_ciclos"() RETURNS trigger AS $$ +BEGIN + IF EXISTS ( + WITH RECURSIVE "sube"("id") AS ( + SELECT NEW."origen_id" + UNION + SELECT "o"."origen_id" FROM "genero_origen" "o" + JOIN "sube" ON "o"."genero_id" = "sube"."id" + ) + SELECT 1 FROM "sube" WHERE "id" = NEW."genero_id" + ) THEN + RAISE EXCEPTION 'Ese origen crearía un ciclo en el linaje de géneros'; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +--> statement-breakpoint +CREATE TRIGGER "genero_sin_ciclos_trg" + BEFORE INSERT OR UPDATE ON "genero_origen" + FOR EACH ROW EXECUTE FUNCTION "genero_sin_ciclos"(); +--> statement-breakpoint + +-- --- La valoración va de uno a cinco ---------------------------------------- +ALTER TABLE "valoracion" ADD CONSTRAINT "valoracion_de_uno_a_cinco" + CHECK ("puntuacion" BETWEEN 1 AND 5); +--> statement-breakpoint + +-- --- El documento legal apunta a su versión vigente -------------------------- +ALTER TABLE "documento_legal" ADD CONSTRAINT "documento_legal_version_vigente_fk" + FOREIGN KEY ("version_vigente_id") REFERENCES "version_legal"("id") ON DELETE set null; +--> statement-breakpoint + +-- --- Una rectificativa apunta a la factura que rectifica --------------------- +ALTER TABLE "factura" ADD CONSTRAINT "factura_rectifica_fk" + FOREIGN KEY ("rectifica_a_id") REFERENCES "factura"("id"); +--> statement-breakpoint +ALTER TABLE "factura" ADD CONSTRAINT "factura_rectificativa_apunta" + CHECK (("tipo" = 'rectificativa') = ("rectifica_a_id" IS NOT NULL)); +--> statement-breakpoint + +-- --- El metro enlaza con el glosario ----------------------------------------- +ALTER TABLE "metro" ADD CONSTRAINT "metro_termino_fk" + FOREIGN KEY ("termino_id") REFERENCES "termino"("id") ON DELETE set null; diff --git a/drizzle/0003_valores_por_defecto.sql b/drizzle/0003_valores_por_defecto.sql new file mode 100644 index 0000000..9547415 --- /dev/null +++ b/drizzle/0003_valores_por_defecto.sql @@ -0,0 +1,29 @@ +ALTER TABLE "medio" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "persona" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "album" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "cancion" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "estilo" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "capitulo" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "genero" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "prompt" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "entrada" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "pagina" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "redireccion" ALTER COLUMN "creada_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "version_legal" ALTER COLUMN "vigente_desde" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "contador_factura" ALTER COLUMN "actualizado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "datos_facturacion" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "descarga" ALTER COLUMN "bajada_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "factura" ALTER COLUMN "fecha" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "codigo_acceso" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "identidad" ALTER COLUMN "creada_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "sesion" ALTER COLUMN "creada_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "usuario" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "carrito" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "carrito" ALTER COLUMN "actualizado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "carrito_item" ALTER COLUMN "anadido_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "compra" ALTER COLUMN "comprado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "pedido" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "favorito" ALTER COLUMN "marcado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "reproduccion" ALTER COLUMN "creado_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "valoracion" ALTER COLUMN "creada_en" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "valoracion" ALTER COLUMN "actualizada_en" SET DEFAULT now(); \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..eec9a85 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,5672 @@ +{ + "id": "b00f074f-d5d7-4158-8e54-08e6e926f2f1", + "prevId": "73fba167-b287-4e75-945d-78ce984184ff", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.medio": { + "name": "medio", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "clase": { + "name": "clase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "clave": { + "name": "clave", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "publico": { + "name": "publico", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mime": { + "name": "mime", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "duracion_s": { + "name": "duracion_s", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ancho": { + "name": "ancho", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "alto": { + "name": "alto", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "alt": { + "name": "alt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "medio_clase": { + "name": "medio_clase", + "columns": [ + { + "expression": "clase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "medio_sha": { + "name": "medio_sha", + "columns": [ + { + "expression": "sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "medio_clave_unique": { + "name": "medio_clave_unique", + "nullsNotDistinct": false, + "columns": [ + "clave" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pais": { + "name": "pais", + "schema": "", + "columns": { + "codigo": { + "name": "codigo", + "type": "char(2)", + "primaryKey": true, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gentilicio": { + "name": "gentilicio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.persona": { + "name": "persona", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre_legal": { + "name": "nombre_legal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipi": { + "name": "ipi", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nacimiento": { + "name": "nacimiento", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "defuncion": { + "name": "defuncion", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "retrato_id": { + "name": "retrato_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "semblanza": { + "name": "semblanza", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "persona_nombre": { + "name": "persona_nombre", + "columns": [ + { + "expression": "nombre", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "persona_pais": { + "name": "persona_pais", + "columns": [ + { + "expression": "pais_codigo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "persona_pais_codigo_pais_codigo_fk": { + "name": "persona_pais_codigo_pais_codigo_fk", + "tableFrom": "persona", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "persona_retrato_id_medio_id_fk": { + "name": "persona_retrato_id_medio_id_fk", + "tableFrom": "persona", + "tableTo": "medio", + "columnsFrom": [ + "retrato_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "persona_slug_unique": { + "name": "persona_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.album": { + "name": "album", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "estilo_id": { + "name": "estilo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'album'" + }, + "fecha": { + "name": "fecha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "portada_id": { + "name": "portada_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sello": { + "name": "sello", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "destacado": { + "name": "destacado", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "numero_catalogo": { + "name": "numero_catalogo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "formatos": { + "name": "formatos", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "pais_edicion_codigo": { + "name": "pais_edicion_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "album_estilo": { + "name": "album_estilo", + "columns": [ + { + "expression": "estilo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "album_fecha": { + "name": "album_fecha", + "columns": [ + { + "expression": "fecha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "album_estilo_id_estilo_id_fk": { + "name": "album_estilo_id_estilo_id_fk", + "tableFrom": "album", + "tableTo": "estilo", + "columnsFrom": [ + "estilo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "album_portada_id_medio_id_fk": { + "name": "album_portada_id_medio_id_fk", + "tableFrom": "album", + "tableTo": "medio", + "columnsFrom": [ + "portada_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "album_pais_edicion_codigo_pais_codigo_fk": { + "name": "album_pais_edicion_codigo_pais_codigo_fk", + "tableFrom": "album", + "tableTo": "pais", + "columnsFrom": [ + "pais_edicion_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "album_slug_unique": { + "name": "album_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.album_enlace": { + "name": "album_enlace", + "schema": "", + "columns": { + "album_id": { + "name": "album_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plataforma": { + "name": "plataforma", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "album_enlace_album_id_album_id_fk": { + "name": "album_enlace_album_id_album_id_fk", + "tableFrom": "album_enlace", + "tableTo": "album", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "album_enlace_album_id_plataforma_pk": { + "name": "album_enlace_album_id_plataforma_pk", + "columns": [ + "album_id", + "plataforma" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.autoria": { + "name": "autoria", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "persona_id": { + "name": "persona_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rol": { + "name": "rol", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "porcentaje": { + "name": "porcentaje", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "autoria_cancion_id_cancion_id_fk": { + "name": "autoria_cancion_id_cancion_id_fk", + "tableFrom": "autoria", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "autoria_persona_id_persona_id_fk": { + "name": "autoria_persona_id_persona_id_fk", + "tableFrom": "autoria", + "tableTo": "persona", + "columnsFrom": [ + "persona_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "autoria_cancion_id_persona_id_rol_pk": { + "name": "autoria_cancion_id_persona_id_rol_pk", + "columns": [ + "cancion_id", + "persona_id", + "rol" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cancion": { + "name": "cancion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_de_id": { + "name": "version_de_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_nombre": { + "name": "version_nombre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_orden": { + "name": "version_orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "estilo_id": { + "name": "estilo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "album_id": { + "name": "album_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "numero": { + "name": "numero", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "audio_id": { + "name": "audio_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "muestra_id": { + "name": "muestra_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duracion_s": { + "name": "duracion_s", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ano": { + "name": "ano", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "letra": { + "name": "letra", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idioma": { + "name": "idioma", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'es'" + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inspiracion": { + "name": "inspiracion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instrumental": { + "name": "instrumental", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "destacada": { + "name": "destacada", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "portada_id": { + "name": "portada_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ala_venta": { + "name": "ala_venta", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "cancion_estilo": { + "name": "cancion_estilo", + "columns": [ + { + "expression": "estilo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cancion_album": { + "name": "cancion_album", + "columns": [ + { + "expression": "album_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cancion_version_de": { + "name": "cancion_version_de", + "columns": [ + { + "expression": "version_de_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cancion_destacada": { + "name": "cancion_destacada", + "columns": [ + { + "expression": "destacada", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cancion_estilo_id_estilo_id_fk": { + "name": "cancion_estilo_id_estilo_id_fk", + "tableFrom": "cancion", + "tableTo": "estilo", + "columnsFrom": [ + "estilo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cancion_album_id_album_id_fk": { + "name": "cancion_album_id_album_id_fk", + "tableFrom": "cancion", + "tableTo": "album", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cancion_audio_id_medio_id_fk": { + "name": "cancion_audio_id_medio_id_fk", + "tableFrom": "cancion", + "tableTo": "medio", + "columnsFrom": [ + "audio_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cancion_muestra_id_medio_id_fk": { + "name": "cancion_muestra_id_medio_id_fk", + "tableFrom": "cancion", + "tableTo": "medio", + "columnsFrom": [ + "muestra_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cancion_portada_id_medio_id_fk": { + "name": "cancion_portada_id_medio_id_fk", + "tableFrom": "cancion", + "tableTo": "medio", + "columnsFrom": [ + "portada_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cancion_slug_unique": { + "name": "cancion_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cancion_enlace": { + "name": "cancion_enlace", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plataforma": { + "name": "plataforma", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "cancion_enlace_cancion_id_cancion_id_fk": { + "name": "cancion_enlace_cancion_id_cancion_id_fk", + "tableFrom": "cancion_enlace", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cancion_enlace_cancion_id_plataforma_pk": { + "name": "cancion_enlace_cancion_id_plataforma_pk", + "columns": [ + "cancion_id", + "plataforma" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cancion_etiqueta": { + "name": "cancion_etiqueta", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "etiqueta_id": { + "name": "etiqueta_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "cancion_etiqueta_cancion_id_cancion_id_fk": { + "name": "cancion_etiqueta_cancion_id_cancion_id_fk", + "tableFrom": "cancion_etiqueta", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cancion_etiqueta_etiqueta_id_etiqueta_id_fk": { + "name": "cancion_etiqueta_etiqueta_id_etiqueta_id_fk", + "tableFrom": "cancion_etiqueta", + "tableTo": "etiqueta", + "columnsFrom": [ + "etiqueta_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cancion_etiqueta_cancion_id_etiqueta_id_pk": { + "name": "cancion_etiqueta_cancion_id_etiqueta_id_pk", + "columns": [ + "cancion_id", + "etiqueta_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cancion_persona": { + "name": "cancion_persona", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "persona_id": { + "name": "persona_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rol": { + "name": "rol", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "cancion_persona_cancion_id_cancion_id_fk": { + "name": "cancion_persona_cancion_id_cancion_id_fk", + "tableFrom": "cancion_persona", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cancion_persona_persona_id_persona_id_fk": { + "name": "cancion_persona_persona_id_persona_id_fk", + "tableFrom": "cancion_persona", + "tableTo": "persona", + "columnsFrom": [ + "persona_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cancion_persona_cancion_id_persona_id_rol_pk": { + "name": "cancion_persona_cancion_id_persona_id_rol_pk", + "columns": [ + "cancion_id", + "persona_id", + "rol" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credito_album": { + "name": "credito_album", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "album_id": { + "name": "album_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "persona_id": { + "name": "persona_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rol": { + "name": "rol", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "credito_album_album": { + "name": "credito_album_album", + "columns": [ + { + "expression": "album_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credito_album_album_id_album_id_fk": { + "name": "credito_album_album_id_album_id_fk", + "tableFrom": "credito_album", + "tableTo": "album", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credito_album_persona_id_persona_id_fk": { + "name": "credito_album_persona_id_persona_id_fk", + "tableFrom": "credito_album", + "tableTo": "persona", + "columnsFrom": [ + "persona_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.estilo": { + "name": "estilo", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "principal": { + "name": "principal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "imagen_id": { + "name": "imagen_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "estilo_principal_unico": { + "name": "estilo_principal_unico", + "columns": [ + { + "expression": "principal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"estilo\".\"principal\"", + "concurrently": false, + "method": "btree", + "with": {} + }, + "estilo_orden": { + "name": "estilo_orden", + "columns": [ + { + "expression": "orden", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "estilo_imagen_id_medio_id_fk": { + "name": "estilo_imagen_id_medio_id_fk", + "tableFrom": "estilo", + "tableTo": "medio", + "columnsFrom": [ + "imagen_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "estilo_slug_unique": { + "name": "estilo_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.etiqueta": { + "name": "etiqueta", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "etiqueta_slug_unique": { + "name": "etiqueta_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registro_obra": { + "name": "registro_obra", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "iswc": { + "name": "iswc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isrc": { + "name": "isrc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entidad": { + "name": "entidad", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "numero": { + "name": "numero", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "editorial": { + "name": "editorial", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "registro_obra_cancion_id_cancion_id_fk": { + "name": "registro_obra_cancion_id_cancion_id_fk", + "tableFrom": "registro_obra", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.traduccion": { + "name": "traduccion", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idioma": { + "name": "idioma", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "texto": { + "name": "texto", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "traduccion_cancion_id_cancion_id_fk": { + "name": "traduccion_cancion_id_cancion_id_fk", + "tableFrom": "traduccion", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "traduccion_cancion_id_idioma_pk": { + "name": "traduccion_cancion_id_idioma_pk", + "columns": [ + "cancion_id", + "idioma" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.video": { + "name": "video", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "youtube_id": { + "name": "youtube_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "video_cancion": { + "name": "video_cancion", + "columns": [ + { + "expression": "cancion_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "video_cancion_id_cancion_id_fk": { + "name": "video_cancion_id_cancion_id_fk", + "tableFrom": "video", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.acepcion": { + "name": "acepcion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "termino_id": { + "name": "termino_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "numero": { + "name": "numero", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "categoria": { + "name": "categoria", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definicion": { + "name": "definicion", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ampliacion": { + "name": "ampliacion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ejemplo": { + "name": "ejemplo", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "acepcion_numero": { + "name": "acepcion_numero", + "columns": [ + { + "expression": "termino_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "numero", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "acepcion_termino_id_termino_id_fk": { + "name": "acepcion_termino_id_termino_id_fk", + "tableFrom": "acepcion", + "tableTo": "termino", + "columnsFrom": [ + "termino_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.animo": { + "name": "animo", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "codigo": { + "name": "codigo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gems": { + "name": "gems", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "valencia": { + "name": "valencia", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "energia": { + "name": "energia", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "descripcion": { + "name": "descripcion", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "animo_codigo_unique": { + "name": "animo_codigo_unique", + "nullsNotDistinct": false, + "columns": [ + "codigo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cancion_genero": { + "name": "cancion_genero", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "cancion_genero_cancion_id_cancion_id_fk": { + "name": "cancion_genero_cancion_id_cancion_id_fk", + "tableFrom": "cancion_genero", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cancion_genero_genero_id_genero_id_fk": { + "name": "cancion_genero_genero_id_genero_id_fk", + "tableFrom": "cancion_genero", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cancion_genero_cancion_id_genero_id_pk": { + "name": "cancion_genero_cancion_id_genero_id_pk", + "columns": [ + "cancion_id", + "genero_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.capitulo": { + "name": "capitulo", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "cuerpo": { + "name": "cuerpo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "capitulo_tipo": { + "name": "capitulo_tipo", + "columns": [ + { + "expression": "tipo", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "orden", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "capitulo_genero_id_genero_id_fk": { + "name": "capitulo_genero_id_genero_id_fk", + "tableFrom": "capitulo", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "capitulo_slug_unique": { + "name": "capitulo_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.capitulo_acepcion": { + "name": "capitulo_acepcion", + "schema": "", + "columns": { + "capitulo_id": { + "name": "capitulo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "acepcion_id": { + "name": "acepcion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "automatico": { + "name": "automatico", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "confirmado": { + "name": "confirmado", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "capitulo_acepcion_capitulo_id_capitulo_id_fk": { + "name": "capitulo_acepcion_capitulo_id_capitulo_id_fk", + "tableFrom": "capitulo_acepcion", + "tableTo": "capitulo", + "columnsFrom": [ + "capitulo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "capitulo_acepcion_acepcion_id_acepcion_id_fk": { + "name": "capitulo_acepcion_acepcion_id_acepcion_id_fk", + "tableFrom": "capitulo_acepcion", + "tableTo": "acepcion", + "columnsFrom": [ + "acepcion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "capitulo_acepcion_capitulo_id_acepcion_id_pk": { + "name": "capitulo_acepcion_capitulo_id_acepcion_id_pk", + "columns": [ + "capitulo_id", + "acepcion_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compas": { + "name": "compas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "codigo": { + "name": "codigo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "numerador": { + "name": "numerador", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "denominador": { + "name": "denominador", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "compas_codigo_unique": { + "name": "compas_codigo_unique", + "nullsNotDistinct": false, + "columns": [ + "codigo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero": { + "name": "genero", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "origen_lugar": { + "name": "origen_lugar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origen_ano": { + "name": "origen_ano", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tempo": { + "name": "tempo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rima": { + "name": "rima", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metrica_nota": { + "name": "metrica_nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "compas_nota": { + "name": "compas_nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rasgos": { + "name": "rasgos", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "origen": { + "name": "origen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "modelo_origen": { + "name": "modelo_origen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "importado_en": { + "name": "importado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revisado_por": { + "name": "revisado_por", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revisado_en": { + "name": "revisado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "genero_nombre": { + "name": "genero_nombre", + "columns": [ + { + "expression": "nombre", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "genero_origen_ano": { + "name": "genero_origen_ano", + "columns": [ + { + "expression": "origen_ano", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "genero_slug_unique": { + "name": "genero_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_alias": { + "name": "genero_alias", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "genero_alias_nombre": { + "name": "genero_alias_nombre", + "columns": [ + { + "expression": "nombre", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "genero_alias_genero_id_genero_id_fk": { + "name": "genero_alias_genero_id_genero_id_fk", + "tableFrom": "genero_alias", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_alias_pais_codigo_pais_codigo_fk": { + "name": "genero_alias_pais_codigo_pais_codigo_fk", + "tableFrom": "genero_alias", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_compas": { + "name": "genero_compas", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "compas_id": { + "name": "compas_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal": { + "name": "principal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "genero_compas_genero_id_genero_id_fk": { + "name": "genero_compas_genero_id_genero_id_fk", + "tableFrom": "genero_compas", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_compas_compas_id_compas_id_fk": { + "name": "genero_compas_compas_id_compas_id_fk", + "tableFrom": "genero_compas", + "tableTo": "compas", + "columnsFrom": [ + "compas_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_compas_genero_id_compas_id_pk": { + "name": "genero_compas_genero_id_compas_id_pk", + "columns": [ + "genero_id", + "compas_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_instrumento": { + "name": "genero_instrumento", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instrumento_id": { + "name": "instrumento_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "importancia": { + "name": "importancia", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'habitual'" + }, + "papel": { + "name": "papel", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "genero_instrumento_genero_id_genero_id_fk": { + "name": "genero_instrumento_genero_id_genero_id_fk", + "tableFrom": "genero_instrumento", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_instrumento_instrumento_id_instrumento_id_fk": { + "name": "genero_instrumento_instrumento_id_instrumento_id_fk", + "tableFrom": "genero_instrumento", + "tableTo": "instrumento", + "columnsFrom": [ + "instrumento_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_instrumento_genero_id_instrumento_id_pk": { + "name": "genero_instrumento_genero_id_instrumento_id_pk", + "columns": [ + "genero_id", + "instrumento_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_metro": { + "name": "genero_metro", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metro_id": { + "name": "metro_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "predominante": { + "name": "predominante", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "genero_metro_genero_id_genero_id_fk": { + "name": "genero_metro_genero_id_genero_id_fk", + "tableFrom": "genero_metro", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_metro_metro_id_metro_id_fk": { + "name": "genero_metro_metro_id_metro_id_fk", + "tableFrom": "genero_metro", + "tableTo": "metro", + "columnsFrom": [ + "metro_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_metro_genero_id_metro_id_pk": { + "name": "genero_metro_genero_id_metro_id_pk", + "columns": [ + "genero_id", + "metro_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_origen": { + "name": "genero_origen", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origen_id": { + "name": "origen_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal": { + "name": "principal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "genero_origen_origen": { + "name": "genero_origen_origen", + "columns": [ + { + "expression": "origen_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "genero_origen_genero_id_genero_id_fk": { + "name": "genero_origen_genero_id_genero_id_fk", + "tableFrom": "genero_origen", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_origen_origen_id_genero_id_fk": { + "name": "genero_origen_origen_id_genero_id_fk", + "tableFrom": "genero_origen", + "tableTo": "genero", + "columnsFrom": [ + "origen_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_origen_genero_id_origen_id_tipo_pk": { + "name": "genero_origen_genero_id_origen_id_tipo_pk", + "columns": [ + "genero_id", + "origen_id", + "tipo" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_pais": { + "name": "genero_pais", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": true + }, + "relacion": { + "name": "relacion", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'adoptado'" + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "genero_pais_genero_id_genero_id_fk": { + "name": "genero_pais_genero_id_genero_id_fk", + "tableFrom": "genero_pais", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_pais_pais_codigo_pais_codigo_fk": { + "name": "genero_pais_pais_codigo_pais_codigo_fk", + "tableFrom": "genero_pais", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_pais_genero_id_pais_codigo_pk": { + "name": "genero_pais_genero_id_pais_codigo_pk", + "columns": [ + "genero_id", + "pais_codigo" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_persona": { + "name": "genero_persona", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "persona_id": { + "name": "persona_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "papel": { + "name": "papel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "genero_persona_genero_id_genero_id_fk": { + "name": "genero_persona_genero_id_genero_id_fk", + "tableFrom": "genero_persona", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_persona_persona_id_persona_id_fk": { + "name": "genero_persona_persona_id_persona_id_fk", + "tableFrom": "genero_persona", + "tableTo": "persona", + "columnsFrom": [ + "persona_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_persona_genero_id_persona_id_papel_pk": { + "name": "genero_persona_genero_id_persona_id_papel_pk", + "columns": [ + "genero_id", + "persona_id", + "papel" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_seccion": { + "name": "genero_seccion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "clave": { + "name": "clave", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cuerpo": { + "name": "cuerpo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "genero_seccion_clave": { + "name": "genero_seccion_clave", + "columns": [ + { + "expression": "genero_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "clave", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "genero_seccion_genero_id_genero_id_fk": { + "name": "genero_seccion_genero_id_genero_id_fk", + "tableFrom": "genero_seccion", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_tema": { + "name": "genero_tema", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "etiqueta_id": { + "name": "etiqueta_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "genero_tema_genero_id_genero_id_fk": { + "name": "genero_tema_genero_id_genero_id_fk", + "tableFrom": "genero_tema", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_tema_etiqueta_id_etiqueta_id_fk": { + "name": "genero_tema_etiqueta_id_etiqueta_id_fk", + "tableFrom": "genero_tema", + "tableTo": "etiqueta", + "columnsFrom": [ + "etiqueta_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_tema_genero_id_etiqueta_id_pk": { + "name": "genero_tema_genero_id_etiqueta_id_pk", + "columns": [ + "genero_id", + "etiqueta_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instrumento": { + "name": "instrumento", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "familia": { + "name": "familia", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subfamilia": { + "name": "subfamilia", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "descripcion": { + "name": "descripcion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "afinacion": { + "name": "afinacion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "imagen_id": { + "name": "imagen_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "muestra_id": { + "name": "muestra_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "origen": { + "name": "origen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "modelo_origen": { + "name": "modelo_origen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "importado_en": { + "name": "importado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revisado_por": { + "name": "revisado_por", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revisado_en": { + "name": "revisado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "instrumento_familia": { + "name": "instrumento_familia", + "columns": [ + { + "expression": "familia", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "instrumento_pais_codigo_pais_codigo_fk": { + "name": "instrumento_pais_codigo_pais_codigo_fk", + "tableFrom": "instrumento", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "instrumento_imagen_id_medio_id_fk": { + "name": "instrumento_imagen_id_medio_id_fk", + "tableFrom": "instrumento", + "tableTo": "medio", + "columnsFrom": [ + "imagen_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "instrumento_muestra_id_medio_id_fk": { + "name": "instrumento_muestra_id_medio_id_fk", + "tableFrom": "instrumento", + "tableTo": "medio", + "columnsFrom": [ + "muestra_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "instrumento_slug_unique": { + "name": "instrumento_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.metro": { + "name": "metro", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "codigo": { + "name": "codigo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "silabas": { + "name": "silabas", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "termino_id": { + "name": "termino_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "metro_codigo_unique": { + "name": "metro_codigo_unique", + "nullsNotDistinct": false, + "columns": [ + "codigo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.modelo_ia": { + "name": "modelo_ia", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "codigo": { + "name": "codigo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "familia": { + "name": "familia", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vigente": { + "name": "vigente", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "nota_sintaxis": { + "name": "nota_sintaxis", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "esquema": { + "name": "esquema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "modelo_ia_familia": { + "name": "modelo_ia_familia", + "columns": [ + { + "expression": "familia", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "modelo_ia_codigo_unique": { + "name": "modelo_ia_codigo_unique", + "nullsNotDistinct": false, + "columns": [ + "codigo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.obra_referencia": { + "name": "obra_referencia", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ano": { + "name": "ano", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enlace": { + "name": "enlace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cita": { + "name": "cita", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "origen": { + "name": "origen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "modelo_origen": { + "name": "modelo_origen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "importado_en": { + "name": "importado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revisado_por": { + "name": "revisado_por", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revisado_en": { + "name": "revisado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "obra_referencia_genero": { + "name": "obra_referencia_genero", + "columns": [ + { + "expression": "genero_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "obra_referencia_genero_id_genero_id_fk": { + "name": "obra_referencia_genero_id_genero_id_fk", + "tableFrom": "obra_referencia", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "obra_referencia_slug_unique": { + "name": "obra_referencia_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.obra_referencia_persona": { + "name": "obra_referencia_persona", + "schema": "", + "columns": { + "obra_id": { + "name": "obra_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "persona_id": { + "name": "persona_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "papel": { + "name": "papel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "obra_referencia_persona_obra_id_obra_referencia_id_fk": { + "name": "obra_referencia_persona_obra_id_obra_referencia_id_fk", + "tableFrom": "obra_referencia_persona", + "tableTo": "obra_referencia", + "columnsFrom": [ + "obra_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "obra_referencia_persona_persona_id_persona_id_fk": { + "name": "obra_referencia_persona_persona_id_persona_id_fk", + "tableFrom": "obra_referencia_persona", + "tableTo": "persona", + "columnsFrom": [ + "persona_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "obra_referencia_persona_obra_id_persona_id_papel_pk": { + "name": "obra_referencia_persona_obra_id_persona_id_papel_pk", + "columns": [ + "obra_id", + "persona_id", + "papel" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.prompt": { + "name": "prompt", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "modelo_ia_id": { + "name": "modelo_ia_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "animo_id": { + "name": "animo_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idioma": { + "name": "idioma", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'en'" + }, + "contenido": { + "name": "contenido", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "notas": { + "name": "notas", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "valoracion": { + "name": "valoracion", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ejemplo_id": { + "name": "ejemplo_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preferido": { + "name": "preferido", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "visibilidad": { + "name": "visibilidad", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'privado'" + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "prompt_genero": { + "name": "prompt_genero", + "columns": [ + { + "expression": "genero_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prompt_modelo": { + "name": "prompt_modelo", + "columns": [ + { + "expression": "modelo_ia_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prompt_animo": { + "name": "prompt_animo", + "columns": [ + { + "expression": "animo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prompt_modelo_ia_id_modelo_ia_id_fk": { + "name": "prompt_modelo_ia_id_modelo_ia_id_fk", + "tableFrom": "prompt", + "tableTo": "modelo_ia", + "columnsFrom": [ + "modelo_ia_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "prompt_genero_id_genero_id_fk": { + "name": "prompt_genero_id_genero_id_fk", + "tableFrom": "prompt", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "prompt_animo_id_animo_id_fk": { + "name": "prompt_animo_id_animo_id_fk", + "tableFrom": "prompt", + "tableTo": "animo", + "columnsFrom": [ + "animo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "prompt_ejemplo_id_medio_id_fk": { + "name": "prompt_ejemplo_id_medio_id_fk", + "tableFrom": "prompt", + "tableTo": "medio", + "columnsFrom": [ + "ejemplo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termino": { + "name": "termino", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "termino": { + "name": "termino", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "termino_slug_unique": { + "name": "termino_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "termino_termino_unique": { + "name": "termino_termino_unique", + "nullsNotDistinct": false, + "columns": [ + "termino" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termino_variante": { + "name": "termino_variante", + "schema": "", + "columns": { + "termino_id": { + "name": "termino_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "forma": { + "name": "forma", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "termino_variante_termino_id_termino_id_fk": { + "name": "termino_variante_termino_id_termino_id_fk", + "tableFrom": "termino_variante", + "tableTo": "termino", + "columnsFrom": [ + "termino_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termino_variante_termino_id_forma_pk": { + "name": "termino_variante_termino_id_forma_pk", + "columns": [ + "termino_id", + "forma" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documento_legal": { + "name": "documento_legal", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "version_vigente_id": { + "name": "version_vigente_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "documento_legal_slug_unique": { + "name": "documento_legal_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entrada": { + "name": "entrada", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fecha": { + "name": "fecha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "cuerpo": { + "name": "cuerpo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "imagen_id": { + "name": "imagen_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "entrada_fecha": { + "name": "entrada_fecha", + "columns": [ + { + "expression": "fecha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entrada_imagen_id_medio_id_fk": { + "name": "entrada_imagen_id_medio_id_fk", + "tableFrom": "entrada", + "tableTo": "medio", + "columnsFrom": [ + "imagen_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entrada_slug_unique": { + "name": "entrada_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entrada_cancion": { + "name": "entrada_cancion", + "schema": "", + "columns": { + "entrada_id": { + "name": "entrada_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "entrada_cancion_entrada_id_entrada_id_fk": { + "name": "entrada_cancion_entrada_id_entrada_id_fk", + "tableFrom": "entrada_cancion", + "tableTo": "entrada", + "columnsFrom": [ + "entrada_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entrada_cancion_cancion_id_cancion_id_fk": { + "name": "entrada_cancion_cancion_id_cancion_id_fk", + "tableFrom": "entrada_cancion", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entrada_cancion_entrada_id_cancion_id_pk": { + "name": "entrada_cancion_entrada_id_cancion_id_pk", + "columns": [ + "entrada_id", + "cancion_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entrada_etiqueta": { + "name": "entrada_etiqueta", + "schema": "", + "columns": { + "entrada_id": { + "name": "entrada_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "etiqueta_id": { + "name": "etiqueta_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "entrada_etiqueta_entrada_id_entrada_id_fk": { + "name": "entrada_etiqueta_entrada_id_entrada_id_fk", + "tableFrom": "entrada_etiqueta", + "tableTo": "entrada", + "columnsFrom": [ + "entrada_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entrada_etiqueta_etiqueta_id_etiqueta_id_fk": { + "name": "entrada_etiqueta_etiqueta_id_etiqueta_id_fk", + "tableFrom": "entrada_etiqueta", + "tableTo": "etiqueta", + "columnsFrom": [ + "etiqueta_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entrada_etiqueta_entrada_id_etiqueta_id_pk": { + "name": "entrada_etiqueta_entrada_id_etiqueta_id_pk", + "columns": [ + "entrada_id", + "etiqueta_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pagina": { + "name": "pagina", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "descripcion": { + "name": "descripcion", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "cuerpo": { + "name": "cuerpo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pagina_slug_unique": { + "name": "pagina_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redireccion": { + "name": "redireccion", + "schema": "", + "columns": { + "desde": { + "name": "desde", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "hacia": { + "name": "hacia", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanente": { + "name": "permanente", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "motivo": { + "name": "motivo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creada_en": { + "name": "creada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "redireccion_hacia": { + "name": "redireccion_hacia", + "columns": [ + { + "expression": "hacia", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.version_legal": { + "name": "version_legal", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "documento_id": { + "name": "documento_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cuerpo": { + "name": "cuerpo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vigente_desde": { + "name": "vigente_desde", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "resumen_cambios": { + "name": "resumen_cambios", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "version_legal_documento": { + "name": "version_legal_documento", + "columns": [ + { + "expression": "documento_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vigente_desde", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "version_legal_documento_id_documento_legal_id_fk": { + "name": "version_legal_documento_id_documento_legal_id_fk", + "tableFrom": "version_legal", + "tableTo": "documento_legal", + "columnsFrom": [ + "documento_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contador_factura": { + "name": "contador_factura", + "schema": "", + "columns": { + "serie": { + "name": "serie", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ultimo_numero": { + "name": "ultimo_numero", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.datos_facturacion": { + "name": "datos_facturacion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'particular'" + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nif": { + "name": "nif", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direccion": { + "name": "direccion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cp": { + "name": "cp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ciudad": { + "name": "ciudad", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provincia": { + "name": "provincia", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": true + }, + "predeterminada": { + "name": "predeterminada", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "datos_facturacion_usuario": { + "name": "datos_facturacion_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "datos_facturacion_usuario_id_usuario_id_fk": { + "name": "datos_facturacion_usuario_id_usuario_id_fk", + "tableFrom": "datos_facturacion", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "datos_facturacion_pais_codigo_pais_codigo_fk": { + "name": "datos_facturacion_pais_codigo_pais_codigo_fk", + "tableFrom": "datos_facturacion", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.descarga": { + "name": "descarga", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bajada_en": { + "name": "bajada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agente": { + "name": "agente", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "descarga_usuario": { + "name": "descarga_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "descarga_fecha": { + "name": "descarga_fecha", + "columns": [ + { + "expression": "bajada_en", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "descarga_usuario_id_usuario_id_fk": { + "name": "descarga_usuario_id_usuario_id_fk", + "tableFrom": "descarga", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.factura": { + "name": "factura", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pedido_id": { + "name": "pedido_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serie": { + "name": "serie", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "numero": { + "name": "numero", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "rectifica_a_id": { + "name": "rectifica_a_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fecha": { + "name": "fecha", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "base_centimos": { + "name": "base_centimos", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "iva_tipo": { + "name": "iva_tipo", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "iva_cuota_centimos": { + "name": "iva_cuota_centimos", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_centimos": { + "name": "total_centimos", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "moneda": { + "name": "moneda", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'eur'" + }, + "datos_congelados": { + "name": "datos_congelados", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "factura_serie_numero": { + "name": "factura_serie_numero", + "columns": [ + { + "expression": "serie", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "numero", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "factura_pedido": { + "name": "factura_pedido", + "columns": [ + { + "expression": "pedido_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "factura_fecha": { + "name": "factura_fecha", + "columns": [ + { + "expression": "fecha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "factura_pedido_id_pedido_id_fk": { + "name": "factura_pedido_id_pedido_id_fk", + "tableFrom": "factura", + "tableTo": "pedido", + "columnsFrom": [ + "pedido_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "factura_pais_codigo_pais_codigo_fk": { + "name": "factura_pais_codigo_pais_codigo_fk", + "tableFrom": "factura", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.codigo_acceso": { + "name": "codigo_acceso", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expira_en": { + "name": "expira_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "intentos": { + "name": "intentos", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "usado_en": { + "name": "usado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "codigo_acceso_email": { + "name": "codigo_acceso_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identidad": { + "name": "identidad", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "proveedor": { + "name": "proveedor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sujeto": { + "name": "sujeto", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creada_en": { + "name": "creada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ultimo_acceso_en": { + "name": "ultimo_acceso_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "identidad_proveedor_sujeto": { + "name": "identidad_proveedor_sujeto", + "columns": [ + { + "expression": "proveedor", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sujeto", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identidad_usuario": { + "name": "identidad_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "identidad_usuario_id_usuario_id_fk": { + "name": "identidad_usuario_id_usuario_id_fk", + "tableFrom": "identidad", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.limite": { + "name": "limite", + "schema": "", + "columns": { + "clave": { + "name": "clave", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "intentos": { + "name": "intentos", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expira_en": { + "name": "expira_en", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "limite_expira": { + "name": "limite_expira", + "columns": [ + { + "expression": "expira_en", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sesion": { + "name": "sesion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expira_en": { + "name": "expira_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "creada_en": { + "name": "creada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "sesion_usuario": { + "name": "sesion_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sesion_usuario_id_usuario_id_fk": { + "name": "sesion_usuario_id_usuario_id_fk", + "tableFrom": "sesion", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usuario": { + "name": "usuario", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ultimo_acceso_en": { + "name": "ultimo_acceso_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "usuario_email_unique": { + "name": "usuario_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.carrito": { + "name": "carrito", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "carrito_usuario": { + "name": "carrito_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "carrito_usuario_id_usuario_id_fk": { + "name": "carrito_usuario_id_usuario_id_fk", + "tableFrom": "carrito", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.carrito_item": { + "name": "carrito_item", + "schema": "", + "columns": { + "carrito_id": { + "name": "carrito_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anadido_en": { + "name": "anadido_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "carrito_item_carrito_id_carrito_id_fk": { + "name": "carrito_item_carrito_id_carrito_id_fk", + "tableFrom": "carrito_item", + "tableTo": "carrito", + "columnsFrom": [ + "carrito_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "carrito_item_carrito_id_cancion_slug_pk": { + "name": "carrito_item_carrito_id_cancion_slug_pk", + "columns": [ + "carrito_id", + "cancion_slug" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compra": { + "name": "compra", + "schema": "", + "columns": { + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pedido_id": { + "name": "pedido_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "comprado_en": { + "name": "comprado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "compra_usuario_id_usuario_id_fk": { + "name": "compra_usuario_id_usuario_id_fk", + "tableFrom": "compra", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compra_pedido_id_pedido_id_fk": { + "name": "compra_pedido_id_pedido_id_fk", + "tableFrom": "compra", + "tableTo": "pedido", + "columnsFrom": [ + "pedido_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "compra_usuario_id_cancion_slug_pk": { + "name": "compra_usuario_id_cancion_slug_pk", + "columns": [ + "usuario_id", + "cancion_slug" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pedido": { + "name": "pedido", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sesion_pago_id": { + "name": "sesion_pago_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pendiente'" + }, + "packs": { + "name": "packs", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_centimos": { + "name": "total_centimos", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "moneda": { + "name": "moneda", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'eur'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "pagado_en": { + "name": "pagado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "pedido_usuario": { + "name": "pedido_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pedido_usuario_id_usuario_id_fk": { + "name": "pedido_usuario_id_usuario_id_fk", + "tableFrom": "pedido", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pedido_sesion_pago_id_unique": { + "name": "pedido_sesion_pago_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sesion_pago_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pedido_item": { + "name": "pedido_item", + "schema": "", + "columns": { + "pedido_id": { + "name": "pedido_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "pedido_item_pedido_id_pedido_id_fk": { + "name": "pedido_item_pedido_id_pedido_id_fk", + "tableFrom": "pedido_item", + "tableTo": "pedido", + "columnsFrom": [ + "pedido_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "pedido_item_pedido_id_cancion_slug_pk": { + "name": "pedido_item_pedido_id_cancion_slug_pk", + "columns": [ + "pedido_id", + "cancion_slug" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.favorito": { + "name": "favorito", + "schema": "", + "columns": { + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "marcado_en": { + "name": "marcado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_favorito_usuario": { + "name": "idx_favorito_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "favorito_usuario_id_usuario_id_fk": { + "name": "favorito_usuario_id_usuario_id_fk", + "tableFrom": "favorito", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "favorito_usuario_id_cancion_slug_pk": { + "name": "favorito_usuario_id_cancion_slug_pk", + "columns": [ + "usuario_id", + "cancion_slug" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reproduccion": { + "name": "reproduccion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "segundos": { + "name": "segundos", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "dia": { + "name": "dia", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "reproduccion_dia": { + "name": "reproduccion_dia", + "columns": [ + { + "expression": "dia", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reproduccion_cancion": { + "name": "reproduccion_cancion", + "columns": [ + { + "expression": "cancion_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reproduccion_usuario": { + "name": "reproduccion_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reproduccion_usuario_id_usuario_id_fk": { + "name": "reproduccion_usuario_id_usuario_id_fk", + "tableFrom": "reproduccion", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.valoracion": { + "name": "valoracion", + "schema": "", + "columns": { + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "puntuacion": { + "name": "puntuacion", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "creada_en": { + "name": "creada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "actualizada_en": { + "name": "actualizada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_valoracion_cancion": { + "name": "idx_valoracion_cancion", + "columns": [ + { + "expression": "cancion_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "valoracion_usuario_id_usuario_id_fk": { + "name": "valoracion_usuario_id_usuario_id_fk", + "tableFrom": "valoracion", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "valoracion_usuario_id_cancion_slug_pk": { + "name": "valoracion_usuario_id_cancion_slug_pk", + "columns": [ + "usuario_id", + "cancion_slug" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..4f584f2 --- /dev/null +++ b/drizzle/meta/0003_snapshot.json @@ -0,0 +1,5701 @@ +{ + "id": "bf0caf39-1bac-491b-aaeb-ee42d57d7f58", + "prevId": "b00f074f-d5d7-4158-8e54-08e6e926f2f1", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.medio": { + "name": "medio", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "clase": { + "name": "clase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "clave": { + "name": "clave", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "publico": { + "name": "publico", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mime": { + "name": "mime", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "duracion_s": { + "name": "duracion_s", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ancho": { + "name": "ancho", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "alto": { + "name": "alto", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "alt": { + "name": "alt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "medio_clase": { + "name": "medio_clase", + "columns": [ + { + "expression": "clase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "medio_sha": { + "name": "medio_sha", + "columns": [ + { + "expression": "sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "medio_clave_unique": { + "name": "medio_clave_unique", + "nullsNotDistinct": false, + "columns": [ + "clave" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pais": { + "name": "pais", + "schema": "", + "columns": { + "codigo": { + "name": "codigo", + "type": "char(2)", + "primaryKey": true, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gentilicio": { + "name": "gentilicio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.persona": { + "name": "persona", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre_legal": { + "name": "nombre_legal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipi": { + "name": "ipi", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nacimiento": { + "name": "nacimiento", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "defuncion": { + "name": "defuncion", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "retrato_id": { + "name": "retrato_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "semblanza": { + "name": "semblanza", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "persona_nombre": { + "name": "persona_nombre", + "columns": [ + { + "expression": "nombre", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "persona_pais": { + "name": "persona_pais", + "columns": [ + { + "expression": "pais_codigo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "persona_pais_codigo_pais_codigo_fk": { + "name": "persona_pais_codigo_pais_codigo_fk", + "tableFrom": "persona", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "persona_retrato_id_medio_id_fk": { + "name": "persona_retrato_id_medio_id_fk", + "tableFrom": "persona", + "tableTo": "medio", + "columnsFrom": [ + "retrato_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "persona_slug_unique": { + "name": "persona_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.album": { + "name": "album", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "estilo_id": { + "name": "estilo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'album'" + }, + "fecha": { + "name": "fecha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "portada_id": { + "name": "portada_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sello": { + "name": "sello", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "destacado": { + "name": "destacado", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "numero_catalogo": { + "name": "numero_catalogo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "formatos": { + "name": "formatos", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "pais_edicion_codigo": { + "name": "pais_edicion_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "album_estilo": { + "name": "album_estilo", + "columns": [ + { + "expression": "estilo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "album_fecha": { + "name": "album_fecha", + "columns": [ + { + "expression": "fecha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "album_estilo_id_estilo_id_fk": { + "name": "album_estilo_id_estilo_id_fk", + "tableFrom": "album", + "tableTo": "estilo", + "columnsFrom": [ + "estilo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "album_portada_id_medio_id_fk": { + "name": "album_portada_id_medio_id_fk", + "tableFrom": "album", + "tableTo": "medio", + "columnsFrom": [ + "portada_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "album_pais_edicion_codigo_pais_codigo_fk": { + "name": "album_pais_edicion_codigo_pais_codigo_fk", + "tableFrom": "album", + "tableTo": "pais", + "columnsFrom": [ + "pais_edicion_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "album_slug_unique": { + "name": "album_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.album_enlace": { + "name": "album_enlace", + "schema": "", + "columns": { + "album_id": { + "name": "album_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plataforma": { + "name": "plataforma", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "album_enlace_album_id_album_id_fk": { + "name": "album_enlace_album_id_album_id_fk", + "tableFrom": "album_enlace", + "tableTo": "album", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "album_enlace_album_id_plataforma_pk": { + "name": "album_enlace_album_id_plataforma_pk", + "columns": [ + "album_id", + "plataforma" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.autoria": { + "name": "autoria", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "persona_id": { + "name": "persona_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rol": { + "name": "rol", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "porcentaje": { + "name": "porcentaje", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "autoria_cancion_id_cancion_id_fk": { + "name": "autoria_cancion_id_cancion_id_fk", + "tableFrom": "autoria", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "autoria_persona_id_persona_id_fk": { + "name": "autoria_persona_id_persona_id_fk", + "tableFrom": "autoria", + "tableTo": "persona", + "columnsFrom": [ + "persona_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "autoria_cancion_id_persona_id_rol_pk": { + "name": "autoria_cancion_id_persona_id_rol_pk", + "columns": [ + "cancion_id", + "persona_id", + "rol" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cancion": { + "name": "cancion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_de_id": { + "name": "version_de_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_nombre": { + "name": "version_nombre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_orden": { + "name": "version_orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "estilo_id": { + "name": "estilo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "album_id": { + "name": "album_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "numero": { + "name": "numero", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "audio_id": { + "name": "audio_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "muestra_id": { + "name": "muestra_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duracion_s": { + "name": "duracion_s", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ano": { + "name": "ano", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "letra": { + "name": "letra", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idioma": { + "name": "idioma", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'es'" + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inspiracion": { + "name": "inspiracion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instrumental": { + "name": "instrumental", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "destacada": { + "name": "destacada", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "portada_id": { + "name": "portada_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ala_venta": { + "name": "ala_venta", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "cancion_estilo": { + "name": "cancion_estilo", + "columns": [ + { + "expression": "estilo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cancion_album": { + "name": "cancion_album", + "columns": [ + { + "expression": "album_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cancion_version_de": { + "name": "cancion_version_de", + "columns": [ + { + "expression": "version_de_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cancion_destacada": { + "name": "cancion_destacada", + "columns": [ + { + "expression": "destacada", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cancion_estilo_id_estilo_id_fk": { + "name": "cancion_estilo_id_estilo_id_fk", + "tableFrom": "cancion", + "tableTo": "estilo", + "columnsFrom": [ + "estilo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cancion_album_id_album_id_fk": { + "name": "cancion_album_id_album_id_fk", + "tableFrom": "cancion", + "tableTo": "album", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cancion_audio_id_medio_id_fk": { + "name": "cancion_audio_id_medio_id_fk", + "tableFrom": "cancion", + "tableTo": "medio", + "columnsFrom": [ + "audio_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cancion_muestra_id_medio_id_fk": { + "name": "cancion_muestra_id_medio_id_fk", + "tableFrom": "cancion", + "tableTo": "medio", + "columnsFrom": [ + "muestra_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cancion_portada_id_medio_id_fk": { + "name": "cancion_portada_id_medio_id_fk", + "tableFrom": "cancion", + "tableTo": "medio", + "columnsFrom": [ + "portada_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cancion_slug_unique": { + "name": "cancion_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cancion_enlace": { + "name": "cancion_enlace", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plataforma": { + "name": "plataforma", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "cancion_enlace_cancion_id_cancion_id_fk": { + "name": "cancion_enlace_cancion_id_cancion_id_fk", + "tableFrom": "cancion_enlace", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cancion_enlace_cancion_id_plataforma_pk": { + "name": "cancion_enlace_cancion_id_plataforma_pk", + "columns": [ + "cancion_id", + "plataforma" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cancion_etiqueta": { + "name": "cancion_etiqueta", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "etiqueta_id": { + "name": "etiqueta_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "cancion_etiqueta_cancion_id_cancion_id_fk": { + "name": "cancion_etiqueta_cancion_id_cancion_id_fk", + "tableFrom": "cancion_etiqueta", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cancion_etiqueta_etiqueta_id_etiqueta_id_fk": { + "name": "cancion_etiqueta_etiqueta_id_etiqueta_id_fk", + "tableFrom": "cancion_etiqueta", + "tableTo": "etiqueta", + "columnsFrom": [ + "etiqueta_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cancion_etiqueta_cancion_id_etiqueta_id_pk": { + "name": "cancion_etiqueta_cancion_id_etiqueta_id_pk", + "columns": [ + "cancion_id", + "etiqueta_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cancion_persona": { + "name": "cancion_persona", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "persona_id": { + "name": "persona_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rol": { + "name": "rol", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "cancion_persona_cancion_id_cancion_id_fk": { + "name": "cancion_persona_cancion_id_cancion_id_fk", + "tableFrom": "cancion_persona", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cancion_persona_persona_id_persona_id_fk": { + "name": "cancion_persona_persona_id_persona_id_fk", + "tableFrom": "cancion_persona", + "tableTo": "persona", + "columnsFrom": [ + "persona_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cancion_persona_cancion_id_persona_id_rol_pk": { + "name": "cancion_persona_cancion_id_persona_id_rol_pk", + "columns": [ + "cancion_id", + "persona_id", + "rol" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credito_album": { + "name": "credito_album", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "album_id": { + "name": "album_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "persona_id": { + "name": "persona_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rol": { + "name": "rol", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "credito_album_album": { + "name": "credito_album_album", + "columns": [ + { + "expression": "album_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credito_album_album_id_album_id_fk": { + "name": "credito_album_album_id_album_id_fk", + "tableFrom": "credito_album", + "tableTo": "album", + "columnsFrom": [ + "album_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credito_album_persona_id_persona_id_fk": { + "name": "credito_album_persona_id_persona_id_fk", + "tableFrom": "credito_album", + "tableTo": "persona", + "columnsFrom": [ + "persona_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.estilo": { + "name": "estilo", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "principal": { + "name": "principal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "imagen_id": { + "name": "imagen_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "estilo_principal_unico": { + "name": "estilo_principal_unico", + "columns": [ + { + "expression": "principal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"estilo\".\"principal\"", + "concurrently": false, + "method": "btree", + "with": {} + }, + "estilo_orden": { + "name": "estilo_orden", + "columns": [ + { + "expression": "orden", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "estilo_imagen_id_medio_id_fk": { + "name": "estilo_imagen_id_medio_id_fk", + "tableFrom": "estilo", + "tableTo": "medio", + "columnsFrom": [ + "imagen_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "estilo_slug_unique": { + "name": "estilo_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.etiqueta": { + "name": "etiqueta", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "etiqueta_slug_unique": { + "name": "etiqueta_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registro_obra": { + "name": "registro_obra", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "iswc": { + "name": "iswc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isrc": { + "name": "isrc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entidad": { + "name": "entidad", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "numero": { + "name": "numero", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "editorial": { + "name": "editorial", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "registro_obra_cancion_id_cancion_id_fk": { + "name": "registro_obra_cancion_id_cancion_id_fk", + "tableFrom": "registro_obra", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.traduccion": { + "name": "traduccion", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idioma": { + "name": "idioma", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "texto": { + "name": "texto", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "traduccion_cancion_id_cancion_id_fk": { + "name": "traduccion_cancion_id_cancion_id_fk", + "tableFrom": "traduccion", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "traduccion_cancion_id_idioma_pk": { + "name": "traduccion_cancion_id_idioma_pk", + "columns": [ + "cancion_id", + "idioma" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.video": { + "name": "video", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "youtube_id": { + "name": "youtube_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "video_cancion": { + "name": "video_cancion", + "columns": [ + { + "expression": "cancion_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "video_cancion_id_cancion_id_fk": { + "name": "video_cancion_id_cancion_id_fk", + "tableFrom": "video", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.acepcion": { + "name": "acepcion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "termino_id": { + "name": "termino_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "numero": { + "name": "numero", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "categoria": { + "name": "categoria", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definicion": { + "name": "definicion", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ampliacion": { + "name": "ampliacion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ejemplo": { + "name": "ejemplo", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "acepcion_numero": { + "name": "acepcion_numero", + "columns": [ + { + "expression": "termino_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "numero", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "acepcion_termino_id_termino_id_fk": { + "name": "acepcion_termino_id_termino_id_fk", + "tableFrom": "acepcion", + "tableTo": "termino", + "columnsFrom": [ + "termino_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.animo": { + "name": "animo", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "codigo": { + "name": "codigo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gems": { + "name": "gems", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "valencia": { + "name": "valencia", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "energia": { + "name": "energia", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "descripcion": { + "name": "descripcion", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "animo_codigo_unique": { + "name": "animo_codigo_unique", + "nullsNotDistinct": false, + "columns": [ + "codigo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cancion_genero": { + "name": "cancion_genero", + "schema": "", + "columns": { + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "cancion_genero_cancion_id_cancion_id_fk": { + "name": "cancion_genero_cancion_id_cancion_id_fk", + "tableFrom": "cancion_genero", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cancion_genero_genero_id_genero_id_fk": { + "name": "cancion_genero_genero_id_genero_id_fk", + "tableFrom": "cancion_genero", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cancion_genero_cancion_id_genero_id_pk": { + "name": "cancion_genero_cancion_id_genero_id_pk", + "columns": [ + "cancion_id", + "genero_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.capitulo": { + "name": "capitulo", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "cuerpo": { + "name": "cuerpo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "capitulo_tipo": { + "name": "capitulo_tipo", + "columns": [ + { + "expression": "tipo", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "orden", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "capitulo_genero_id_genero_id_fk": { + "name": "capitulo_genero_id_genero_id_fk", + "tableFrom": "capitulo", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "capitulo_slug_unique": { + "name": "capitulo_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.capitulo_acepcion": { + "name": "capitulo_acepcion", + "schema": "", + "columns": { + "capitulo_id": { + "name": "capitulo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "acepcion_id": { + "name": "acepcion_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "automatico": { + "name": "automatico", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "confirmado": { + "name": "confirmado", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "capitulo_acepcion_capitulo_id_capitulo_id_fk": { + "name": "capitulo_acepcion_capitulo_id_capitulo_id_fk", + "tableFrom": "capitulo_acepcion", + "tableTo": "capitulo", + "columnsFrom": [ + "capitulo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "capitulo_acepcion_acepcion_id_acepcion_id_fk": { + "name": "capitulo_acepcion_acepcion_id_acepcion_id_fk", + "tableFrom": "capitulo_acepcion", + "tableTo": "acepcion", + "columnsFrom": [ + "acepcion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "capitulo_acepcion_capitulo_id_acepcion_id_pk": { + "name": "capitulo_acepcion_capitulo_id_acepcion_id_pk", + "columns": [ + "capitulo_id", + "acepcion_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compas": { + "name": "compas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "codigo": { + "name": "codigo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "numerador": { + "name": "numerador", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "denominador": { + "name": "denominador", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "compas_codigo_unique": { + "name": "compas_codigo_unique", + "nullsNotDistinct": false, + "columns": [ + "codigo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero": { + "name": "genero", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "origen_lugar": { + "name": "origen_lugar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origen_ano": { + "name": "origen_ano", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tempo": { + "name": "tempo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rima": { + "name": "rima", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metrica_nota": { + "name": "metrica_nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "compas_nota": { + "name": "compas_nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rasgos": { + "name": "rasgos", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "origen": { + "name": "origen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "modelo_origen": { + "name": "modelo_origen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "importado_en": { + "name": "importado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revisado_por": { + "name": "revisado_por", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revisado_en": { + "name": "revisado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "genero_nombre": { + "name": "genero_nombre", + "columns": [ + { + "expression": "nombre", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "genero_origen_ano": { + "name": "genero_origen_ano", + "columns": [ + { + "expression": "origen_ano", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "genero_slug_unique": { + "name": "genero_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_alias": { + "name": "genero_alias", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "genero_alias_nombre": { + "name": "genero_alias_nombre", + "columns": [ + { + "expression": "nombre", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "genero_alias_genero_id_genero_id_fk": { + "name": "genero_alias_genero_id_genero_id_fk", + "tableFrom": "genero_alias", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_alias_pais_codigo_pais_codigo_fk": { + "name": "genero_alias_pais_codigo_pais_codigo_fk", + "tableFrom": "genero_alias", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_compas": { + "name": "genero_compas", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "compas_id": { + "name": "compas_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal": { + "name": "principal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "genero_compas_genero_id_genero_id_fk": { + "name": "genero_compas_genero_id_genero_id_fk", + "tableFrom": "genero_compas", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_compas_compas_id_compas_id_fk": { + "name": "genero_compas_compas_id_compas_id_fk", + "tableFrom": "genero_compas", + "tableTo": "compas", + "columnsFrom": [ + "compas_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_compas_genero_id_compas_id_pk": { + "name": "genero_compas_genero_id_compas_id_pk", + "columns": [ + "genero_id", + "compas_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_instrumento": { + "name": "genero_instrumento", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instrumento_id": { + "name": "instrumento_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "importancia": { + "name": "importancia", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'habitual'" + }, + "papel": { + "name": "papel", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "genero_instrumento_genero_id_genero_id_fk": { + "name": "genero_instrumento_genero_id_genero_id_fk", + "tableFrom": "genero_instrumento", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_instrumento_instrumento_id_instrumento_id_fk": { + "name": "genero_instrumento_instrumento_id_instrumento_id_fk", + "tableFrom": "genero_instrumento", + "tableTo": "instrumento", + "columnsFrom": [ + "instrumento_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_instrumento_genero_id_instrumento_id_pk": { + "name": "genero_instrumento_genero_id_instrumento_id_pk", + "columns": [ + "genero_id", + "instrumento_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_metro": { + "name": "genero_metro", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metro_id": { + "name": "metro_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "predominante": { + "name": "predominante", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "genero_metro_genero_id_genero_id_fk": { + "name": "genero_metro_genero_id_genero_id_fk", + "tableFrom": "genero_metro", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_metro_metro_id_metro_id_fk": { + "name": "genero_metro_metro_id_metro_id_fk", + "tableFrom": "genero_metro", + "tableTo": "metro", + "columnsFrom": [ + "metro_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_metro_genero_id_metro_id_pk": { + "name": "genero_metro_genero_id_metro_id_pk", + "columns": [ + "genero_id", + "metro_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_origen": { + "name": "genero_origen", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origen_id": { + "name": "origen_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal": { + "name": "principal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "genero_origen_origen": { + "name": "genero_origen_origen", + "columns": [ + { + "expression": "origen_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "genero_origen_genero_id_genero_id_fk": { + "name": "genero_origen_genero_id_genero_id_fk", + "tableFrom": "genero_origen", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_origen_origen_id_genero_id_fk": { + "name": "genero_origen_origen_id_genero_id_fk", + "tableFrom": "genero_origen", + "tableTo": "genero", + "columnsFrom": [ + "origen_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_origen_genero_id_origen_id_tipo_pk": { + "name": "genero_origen_genero_id_origen_id_tipo_pk", + "columns": [ + "genero_id", + "origen_id", + "tipo" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_pais": { + "name": "genero_pais", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": true + }, + "relacion": { + "name": "relacion", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'adoptado'" + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "genero_pais_genero_id_genero_id_fk": { + "name": "genero_pais_genero_id_genero_id_fk", + "tableFrom": "genero_pais", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_pais_pais_codigo_pais_codigo_fk": { + "name": "genero_pais_pais_codigo_pais_codigo_fk", + "tableFrom": "genero_pais", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_pais_genero_id_pais_codigo_pk": { + "name": "genero_pais_genero_id_pais_codigo_pk", + "columns": [ + "genero_id", + "pais_codigo" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_persona": { + "name": "genero_persona", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "persona_id": { + "name": "persona_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "papel": { + "name": "papel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "genero_persona_genero_id_genero_id_fk": { + "name": "genero_persona_genero_id_genero_id_fk", + "tableFrom": "genero_persona", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_persona_persona_id_persona_id_fk": { + "name": "genero_persona_persona_id_persona_id_fk", + "tableFrom": "genero_persona", + "tableTo": "persona", + "columnsFrom": [ + "persona_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_persona_genero_id_persona_id_papel_pk": { + "name": "genero_persona_genero_id_persona_id_papel_pk", + "columns": [ + "genero_id", + "persona_id", + "papel" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_seccion": { + "name": "genero_seccion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "clave": { + "name": "clave", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cuerpo": { + "name": "cuerpo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "genero_seccion_clave": { + "name": "genero_seccion_clave", + "columns": [ + { + "expression": "genero_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "clave", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "genero_seccion_genero_id_genero_id_fk": { + "name": "genero_seccion_genero_id_genero_id_fk", + "tableFrom": "genero_seccion", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genero_tema": { + "name": "genero_tema", + "schema": "", + "columns": { + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "etiqueta_id": { + "name": "etiqueta_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "genero_tema_genero_id_genero_id_fk": { + "name": "genero_tema_genero_id_genero_id_fk", + "tableFrom": "genero_tema", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "genero_tema_etiqueta_id_etiqueta_id_fk": { + "name": "genero_tema_etiqueta_id_etiqueta_id_fk", + "tableFrom": "genero_tema", + "tableTo": "etiqueta", + "columnsFrom": [ + "etiqueta_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "genero_tema_genero_id_etiqueta_id_pk": { + "name": "genero_tema_genero_id_etiqueta_id_pk", + "columns": [ + "genero_id", + "etiqueta_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instrumento": { + "name": "instrumento", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "familia": { + "name": "familia", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subfamilia": { + "name": "subfamilia", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "descripcion": { + "name": "descripcion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "afinacion": { + "name": "afinacion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "imagen_id": { + "name": "imagen_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "muestra_id": { + "name": "muestra_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "origen": { + "name": "origen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "modelo_origen": { + "name": "modelo_origen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "importado_en": { + "name": "importado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revisado_por": { + "name": "revisado_por", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revisado_en": { + "name": "revisado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "instrumento_familia": { + "name": "instrumento_familia", + "columns": [ + { + "expression": "familia", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "instrumento_pais_codigo_pais_codigo_fk": { + "name": "instrumento_pais_codigo_pais_codigo_fk", + "tableFrom": "instrumento", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "instrumento_imagen_id_medio_id_fk": { + "name": "instrumento_imagen_id_medio_id_fk", + "tableFrom": "instrumento", + "tableTo": "medio", + "columnsFrom": [ + "imagen_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "instrumento_muestra_id_medio_id_fk": { + "name": "instrumento_muestra_id_medio_id_fk", + "tableFrom": "instrumento", + "tableTo": "medio", + "columnsFrom": [ + "muestra_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "instrumento_slug_unique": { + "name": "instrumento_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.metro": { + "name": "metro", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "codigo": { + "name": "codigo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "silabas": { + "name": "silabas", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "termino_id": { + "name": "termino_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "metro_codigo_unique": { + "name": "metro_codigo_unique", + "nullsNotDistinct": false, + "columns": [ + "codigo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.modelo_ia": { + "name": "modelo_ia", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "codigo": { + "name": "codigo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "familia": { + "name": "familia", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vigente": { + "name": "vigente", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "nota_sintaxis": { + "name": "nota_sintaxis", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "esquema": { + "name": "esquema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "modelo_ia_familia": { + "name": "modelo_ia_familia", + "columns": [ + { + "expression": "familia", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "modelo_ia_codigo_unique": { + "name": "modelo_ia_codigo_unique", + "nullsNotDistinct": false, + "columns": [ + "codigo" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.obra_referencia": { + "name": "obra_referencia", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ano": { + "name": "ano", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nota": { + "name": "nota", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enlace": { + "name": "enlace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cita": { + "name": "cita", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "origen": { + "name": "origen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "modelo_origen": { + "name": "modelo_origen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "importado_en": { + "name": "importado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revisado_por": { + "name": "revisado_por", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revisado_en": { + "name": "revisado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "obra_referencia_genero": { + "name": "obra_referencia_genero", + "columns": [ + { + "expression": "genero_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "obra_referencia_genero_id_genero_id_fk": { + "name": "obra_referencia_genero_id_genero_id_fk", + "tableFrom": "obra_referencia", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "obra_referencia_slug_unique": { + "name": "obra_referencia_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.obra_referencia_persona": { + "name": "obra_referencia_persona", + "schema": "", + "columns": { + "obra_id": { + "name": "obra_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "persona_id": { + "name": "persona_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "papel": { + "name": "papel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "obra_referencia_persona_obra_id_obra_referencia_id_fk": { + "name": "obra_referencia_persona_obra_id_obra_referencia_id_fk", + "tableFrom": "obra_referencia_persona", + "tableTo": "obra_referencia", + "columnsFrom": [ + "obra_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "obra_referencia_persona_persona_id_persona_id_fk": { + "name": "obra_referencia_persona_persona_id_persona_id_fk", + "tableFrom": "obra_referencia_persona", + "tableTo": "persona", + "columnsFrom": [ + "persona_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "obra_referencia_persona_obra_id_persona_id_papel_pk": { + "name": "obra_referencia_persona_obra_id_persona_id_papel_pk", + "columns": [ + "obra_id", + "persona_id", + "papel" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.prompt": { + "name": "prompt", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "modelo_ia_id": { + "name": "modelo_ia_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "genero_id": { + "name": "genero_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "animo_id": { + "name": "animo_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idioma": { + "name": "idioma", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'en'" + }, + "contenido": { + "name": "contenido", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "notas": { + "name": "notas", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "valoracion": { + "name": "valoracion", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ejemplo_id": { + "name": "ejemplo_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preferido": { + "name": "preferido", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "visibilidad": { + "name": "visibilidad", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'privado'" + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "prompt_genero": { + "name": "prompt_genero", + "columns": [ + { + "expression": "genero_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prompt_modelo": { + "name": "prompt_modelo", + "columns": [ + { + "expression": "modelo_ia_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "prompt_animo": { + "name": "prompt_animo", + "columns": [ + { + "expression": "animo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prompt_modelo_ia_id_modelo_ia_id_fk": { + "name": "prompt_modelo_ia_id_modelo_ia_id_fk", + "tableFrom": "prompt", + "tableTo": "modelo_ia", + "columnsFrom": [ + "modelo_ia_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "prompt_genero_id_genero_id_fk": { + "name": "prompt_genero_id_genero_id_fk", + "tableFrom": "prompt", + "tableTo": "genero", + "columnsFrom": [ + "genero_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "prompt_animo_id_animo_id_fk": { + "name": "prompt_animo_id_animo_id_fk", + "tableFrom": "prompt", + "tableTo": "animo", + "columnsFrom": [ + "animo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "prompt_ejemplo_id_medio_id_fk": { + "name": "prompt_ejemplo_id_medio_id_fk", + "tableFrom": "prompt", + "tableTo": "medio", + "columnsFrom": [ + "ejemplo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termino": { + "name": "termino", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "termino": { + "name": "termino", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "termino_slug_unique": { + "name": "termino_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "termino_termino_unique": { + "name": "termino_termino_unique", + "nullsNotDistinct": false, + "columns": [ + "termino" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termino_variante": { + "name": "termino_variante", + "schema": "", + "columns": { + "termino_id": { + "name": "termino_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "forma": { + "name": "forma", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "termino_variante_termino_id_termino_id_fk": { + "name": "termino_variante_termino_id_termino_id_fk", + "tableFrom": "termino_variante", + "tableTo": "termino", + "columnsFrom": [ + "termino_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termino_variante_termino_id_forma_pk": { + "name": "termino_variante_termino_id_forma_pk", + "columns": [ + "termino_id", + "forma" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documento_legal": { + "name": "documento_legal", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "orden": { + "name": "orden", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "version_vigente_id": { + "name": "version_vigente_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "documento_legal_slug_unique": { + "name": "documento_legal_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entrada": { + "name": "entrada", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fecha": { + "name": "fecha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resumen": { + "name": "resumen", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "cuerpo": { + "name": "cuerpo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "imagen_id": { + "name": "imagen_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "entrada_fecha": { + "name": "entrada_fecha", + "columns": [ + { + "expression": "fecha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entrada_imagen_id_medio_id_fk": { + "name": "entrada_imagen_id_medio_id_fk", + "tableFrom": "entrada", + "tableTo": "medio", + "columnsFrom": [ + "imagen_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entrada_slug_unique": { + "name": "entrada_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entrada_cancion": { + "name": "entrada_cancion", + "schema": "", + "columns": { + "entrada_id": { + "name": "entrada_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_id": { + "name": "cancion_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "entrada_cancion_entrada_id_entrada_id_fk": { + "name": "entrada_cancion_entrada_id_entrada_id_fk", + "tableFrom": "entrada_cancion", + "tableTo": "entrada", + "columnsFrom": [ + "entrada_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entrada_cancion_cancion_id_cancion_id_fk": { + "name": "entrada_cancion_cancion_id_cancion_id_fk", + "tableFrom": "entrada_cancion", + "tableTo": "cancion", + "columnsFrom": [ + "cancion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entrada_cancion_entrada_id_cancion_id_pk": { + "name": "entrada_cancion_entrada_id_cancion_id_pk", + "columns": [ + "entrada_id", + "cancion_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entrada_etiqueta": { + "name": "entrada_etiqueta", + "schema": "", + "columns": { + "entrada_id": { + "name": "entrada_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "etiqueta_id": { + "name": "etiqueta_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "entrada_etiqueta_entrada_id_entrada_id_fk": { + "name": "entrada_etiqueta_entrada_id_entrada_id_fk", + "tableFrom": "entrada_etiqueta", + "tableTo": "entrada", + "columnsFrom": [ + "entrada_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entrada_etiqueta_etiqueta_id_etiqueta_id_fk": { + "name": "entrada_etiqueta_etiqueta_id_etiqueta_id_fk", + "tableFrom": "entrada_etiqueta", + "tableTo": "etiqueta", + "columnsFrom": [ + "etiqueta_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entrada_etiqueta_entrada_id_etiqueta_id_pk": { + "name": "entrada_etiqueta_entrada_id_etiqueta_id_pk", + "columns": [ + "entrada_id", + "etiqueta_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pagina": { + "name": "pagina", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "descripcion": { + "name": "descripcion", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "cuerpo": { + "name": "cuerpo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'borrador'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pagina_slug_unique": { + "name": "pagina_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redireccion": { + "name": "redireccion", + "schema": "", + "columns": { + "desde": { + "name": "desde", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "hacia": { + "name": "hacia", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanente": { + "name": "permanente", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "motivo": { + "name": "motivo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creada_en": { + "name": "creada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "redireccion_hacia": { + "name": "redireccion_hacia", + "columns": [ + { + "expression": "hacia", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.version_legal": { + "name": "version_legal", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "documento_id": { + "name": "documento_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cuerpo": { + "name": "cuerpo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vigente_desde": { + "name": "vigente_desde", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resumen_cambios": { + "name": "resumen_cambios", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "version_legal_documento": { + "name": "version_legal_documento", + "columns": [ + { + "expression": "documento_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vigente_desde", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "version_legal_documento_id_documento_legal_id_fk": { + "name": "version_legal_documento_id_documento_legal_id_fk", + "tableFrom": "version_legal", + "tableTo": "documento_legal", + "columnsFrom": [ + "documento_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contador_factura": { + "name": "contador_factura", + "schema": "", + "columns": { + "serie": { + "name": "serie", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ultimo_numero": { + "name": "ultimo_numero", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.datos_facturacion": { + "name": "datos_facturacion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'particular'" + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nif": { + "name": "nif", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direccion": { + "name": "direccion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cp": { + "name": "cp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ciudad": { + "name": "ciudad", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provincia": { + "name": "provincia", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": true + }, + "predeterminada": { + "name": "predeterminada", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "datos_facturacion_usuario": { + "name": "datos_facturacion_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "datos_facturacion_usuario_id_usuario_id_fk": { + "name": "datos_facturacion_usuario_id_usuario_id_fk", + "tableFrom": "datos_facturacion", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "datos_facturacion_pais_codigo_pais_codigo_fk": { + "name": "datos_facturacion_pais_codigo_pais_codigo_fk", + "tableFrom": "datos_facturacion", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.descarga": { + "name": "descarga", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bajada_en": { + "name": "bajada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agente": { + "name": "agente", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "descarga_usuario": { + "name": "descarga_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "descarga_fecha": { + "name": "descarga_fecha", + "columns": [ + { + "expression": "bajada_en", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "descarga_usuario_id_usuario_id_fk": { + "name": "descarga_usuario_id_usuario_id_fk", + "tableFrom": "descarga", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.factura": { + "name": "factura", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "pedido_id": { + "name": "pedido_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serie": { + "name": "serie", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "numero": { + "name": "numero", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "rectifica_a_id": { + "name": "rectifica_a_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fecha": { + "name": "fecha", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "base_centimos": { + "name": "base_centimos", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "iva_tipo": { + "name": "iva_tipo", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "iva_cuota_centimos": { + "name": "iva_cuota_centimos", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_centimos": { + "name": "total_centimos", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "moneda": { + "name": "moneda", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'eur'" + }, + "datos_congelados": { + "name": "datos_congelados", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pais_codigo": { + "name": "pais_codigo", + "type": "char(2)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "factura_serie_numero": { + "name": "factura_serie_numero", + "columns": [ + { + "expression": "serie", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "numero", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "factura_pedido": { + "name": "factura_pedido", + "columns": [ + { + "expression": "pedido_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "factura_fecha": { + "name": "factura_fecha", + "columns": [ + { + "expression": "fecha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "factura_pedido_id_pedido_id_fk": { + "name": "factura_pedido_id_pedido_id_fk", + "tableFrom": "factura", + "tableTo": "pedido", + "columnsFrom": [ + "pedido_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "factura_pais_codigo_pais_codigo_fk": { + "name": "factura_pais_codigo_pais_codigo_fk", + "tableFrom": "factura", + "tableTo": "pais", + "columnsFrom": [ + "pais_codigo" + ], + "columnsTo": [ + "codigo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.codigo_acceso": { + "name": "codigo_acceso", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expira_en": { + "name": "expira_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "intentos": { + "name": "intentos", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "usado_en": { + "name": "usado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "codigo_acceso_email": { + "name": "codigo_acceso_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identidad": { + "name": "identidad", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "proveedor": { + "name": "proveedor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sujeto": { + "name": "sujeto", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creada_en": { + "name": "creada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ultimo_acceso_en": { + "name": "ultimo_acceso_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "identidad_proveedor_sujeto": { + "name": "identidad_proveedor_sujeto", + "columns": [ + { + "expression": "proveedor", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sujeto", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identidad_usuario": { + "name": "identidad_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "identidad_usuario_id_usuario_id_fk": { + "name": "identidad_usuario_id_usuario_id_fk", + "tableFrom": "identidad", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.limite": { + "name": "limite", + "schema": "", + "columns": { + "clave": { + "name": "clave", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "intentos": { + "name": "intentos", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expira_en": { + "name": "expira_en", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "limite_expira": { + "name": "limite_expira", + "columns": [ + { + "expression": "expira_en", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sesion": { + "name": "sesion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expira_en": { + "name": "expira_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "creada_en": { + "name": "creada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sesion_usuario": { + "name": "sesion_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sesion_usuario_id_usuario_id_fk": { + "name": "sesion_usuario_id_usuario_id_fk", + "tableFrom": "sesion", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usuario": { + "name": "usuario", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nombre": { + "name": "nombre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ultimo_acceso_en": { + "name": "ultimo_acceso_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "usuario_email_unique": { + "name": "usuario_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.carrito": { + "name": "carrito", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizado_en": { + "name": "actualizado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "carrito_usuario": { + "name": "carrito_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "carrito_usuario_id_usuario_id_fk": { + "name": "carrito_usuario_id_usuario_id_fk", + "tableFrom": "carrito", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.carrito_item": { + "name": "carrito_item", + "schema": "", + "columns": { + "carrito_id": { + "name": "carrito_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anadido_en": { + "name": "anadido_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "carrito_item_carrito_id_carrito_id_fk": { + "name": "carrito_item_carrito_id_carrito_id_fk", + "tableFrom": "carrito_item", + "tableTo": "carrito", + "columnsFrom": [ + "carrito_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "carrito_item_carrito_id_cancion_slug_pk": { + "name": "carrito_item_carrito_id_cancion_slug_pk", + "columns": [ + "carrito_id", + "cancion_slug" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compra": { + "name": "compra", + "schema": "", + "columns": { + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pedido_id": { + "name": "pedido_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "comprado_en": { + "name": "comprado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "compra_usuario_id_usuario_id_fk": { + "name": "compra_usuario_id_usuario_id_fk", + "tableFrom": "compra", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compra_pedido_id_pedido_id_fk": { + "name": "compra_pedido_id_pedido_id_fk", + "tableFrom": "compra", + "tableTo": "pedido", + "columnsFrom": [ + "pedido_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "compra_usuario_id_cancion_slug_pk": { + "name": "compra_usuario_id_cancion_slug_pk", + "columns": [ + "usuario_id", + "cancion_slug" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pedido": { + "name": "pedido", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sesion_pago_id": { + "name": "sesion_pago_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "estado": { + "name": "estado", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pendiente'" + }, + "packs": { + "name": "packs", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_centimos": { + "name": "total_centimos", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "moneda": { + "name": "moneda", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'eur'" + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pagado_en": { + "name": "pagado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "pedido_usuario": { + "name": "pedido_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pedido_usuario_id_usuario_id_fk": { + "name": "pedido_usuario_id_usuario_id_fk", + "tableFrom": "pedido", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pedido_sesion_pago_id_unique": { + "name": "pedido_sesion_pago_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sesion_pago_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pedido_item": { + "name": "pedido_item", + "schema": "", + "columns": { + "pedido_id": { + "name": "pedido_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "pedido_item_pedido_id_pedido_id_fk": { + "name": "pedido_item_pedido_id_pedido_id_fk", + "tableFrom": "pedido_item", + "tableTo": "pedido", + "columnsFrom": [ + "pedido_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "pedido_item_pedido_id_cancion_slug_pk": { + "name": "pedido_item_pedido_id_cancion_slug_pk", + "columns": [ + "pedido_id", + "cancion_slug" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.favorito": { + "name": "favorito", + "schema": "", + "columns": { + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "marcado_en": { + "name": "marcado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_favorito_usuario": { + "name": "idx_favorito_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "favorito_usuario_id_usuario_id_fk": { + "name": "favorito_usuario_id_usuario_id_fk", + "tableFrom": "favorito", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "favorito_usuario_id_cancion_slug_pk": { + "name": "favorito_usuario_id_cancion_slug_pk", + "columns": [ + "usuario_id", + "cancion_slug" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reproduccion": { + "name": "reproduccion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "segundos": { + "name": "segundos", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "dia": { + "name": "dia", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creado_en": { + "name": "creado_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reproduccion_dia": { + "name": "reproduccion_dia", + "columns": [ + { + "expression": "dia", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reproduccion_cancion": { + "name": "reproduccion_cancion", + "columns": [ + { + "expression": "cancion_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reproduccion_usuario": { + "name": "reproduccion_usuario", + "columns": [ + { + "expression": "usuario_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reproduccion_usuario_id_usuario_id_fk": { + "name": "reproduccion_usuario_id_usuario_id_fk", + "tableFrom": "reproduccion", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.valoracion": { + "name": "valoracion", + "schema": "", + "columns": { + "usuario_id": { + "name": "usuario_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancion_slug": { + "name": "cancion_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "puntuacion": { + "name": "puntuacion", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "creada_en": { + "name": "creada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actualizada_en": { + "name": "actualizada_en", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_valoracion_cancion": { + "name": "idx_valoracion_cancion", + "columns": [ + { + "expression": "cancion_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "valoracion_usuario_id_usuario_id_fk": { + "name": "valoracion_usuario_id_usuario_id_fk", + "tableFrom": "valoracion", + "tableTo": "usuario", + "columnsFrom": [ + "usuario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "valoracion_usuario_id_cancion_slug_pk": { + "name": "valoracion_usuario_id_cancion_slug_pk", + "columns": [ + "usuario_id", + "cancion_slug" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 984bab7..30f725a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,20 @@ "when": 1788820606303, "tag": "0001_identidades", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1788876298322, + "tag": "0002_catalogo_en_la_base", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1788878096657, + "tag": "0003_valores_por_defecto", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package.json b/package.json index b84498a..08815ce 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "db:studio": "drizzle-kit studio", "medios": "node scripts/medios.mjs", "predev": "node scripts/medios.mjs", - "prebuild": "node scripts/medios.mjs" + "prebuild": "node scripts/medios.mjs", + "db:guardas": "node --env-file=.env scripts/db/comprobar-guardas.mjs", + "db:probar": "node --env-file=.env scripts/db/probar-migracion.mjs" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/scripts/db/comprobar-guardas.mjs b/scripts/db/comprobar-guardas.mjs new file mode 100644 index 0000000..7c94f40 --- /dev/null +++ b/scripts/db/comprobar-guardas.mjs @@ -0,0 +1,223 @@ +/** + * Comprueba que las guardas del esquema hacen lo que dicen. + * + * Que una restricción exista en `pg_constraint` no significa que impida nada: + * se prueba intentando lo que tiene que rechazar. Todo va dentro de una + * transacción que se deshace, así que no deja rastro. + */ +import fs from 'node:fs'; +import pg from 'pg'; + +const DIARIO = '.diario-guardas.txt'; +fs.writeFileSync(DIARIO, ''); +const apuntar = (l) => fs.appendFileSync(DIARIO, l + '\n'); + +const c = new pg.Client({ connectionString: process.env.DATABASE_URL, query_timeout: 15_000 }); +await c.connect(); +await c.query('BEGIN'); + +const resumen = await c.query(` + select + (select count(*)::int from information_schema.tables + where table_schema = 'public' and table_type = 'BASE TABLE') as tablas, + (select count(*)::int from information_schema.views + where table_schema = 'public') as vistas, + (select count(*)::int from information_schema.table_constraints + where constraint_schema = 'public' and constraint_type = 'FOREIGN KEY') as fks, + (select count(*)::int from pg_trigger where not tgisinternal) as disparadores`); + +const r = resumen.rows[0]; +apuntar( + `tablas ${r.tablas} · vistas ${r.vistas} · claves foráneas ${r.fks} · disparadores ${r.disparadores}\n` +); + +/** Prueba que algo se rechaza. Si pasa, es que la guarda no guarda. */ +async function rechaza(nombre, sql, valores = []) { + await c.query('SAVEPOINT prueba'); + try { + await c.query(sql, valores); + await c.query('ROLLBACK TO SAVEPOINT prueba'); + apuntar(` ✗ ${nombre} — SE PERMITIÓ, y no debería`); + return false; + } catch (fallo) { + await c.query('ROLLBACK TO SAVEPOINT prueba'); + const motivo = fallo.message.split('\n')[0].slice(0, 70); + apuntar(` ✓ ${nombre} — rechazado: ${motivo}`); + return true; + } +} + +/** + * Como `rechaza`, pero forzando la comprobación de las restricciones + * aplazadas. + * + * El disparador del reparto de autoría es `DEFERRABLE INITIALLY DEFERRED`, y + * con razón: al insertar tres autores, los estados intermedios no suman cien y + * comprobarlo fila a fila haría imposible cualquier inserción. Pero eso + * significa que solo salta al cerrar la transacción, y una prueba que nunca + * cierra no lo despierta: la primera versión de esto daba por buena una guarda + * que no había llegado a ejecutarse. + */ +async function rechazaAlCerrar(nombre, preparar, sql) { + /* + * Conexión propia y transacción propia. Dentro de un savepoint, + * `SET CONSTRAINTS ALL IMMEDIATE` no despertaba los eventos aplazados y la + * prueba daba por bueno lo que el disparador sí rechaza —comprobado + * interrogándolo aparte—. Una guarda que solo se puede comprobar al cerrar + * necesita una transacción que se pueda cerrar. + */ + const otro = new pg.Client({ connectionString: process.env.DATABASE_URL, query_timeout: 15_000 }); + await otro.connect(); + await otro.query('BEGIN'); + try { + for (const linea of preparar) await otro.query(linea); + await otro.query(sql); + await otro.query('SET CONSTRAINTS ALL IMMEDIATE'); + apuntar(` ✗ ${nombre} — SE PERMITIÓ, y no debería`); + return false; + } catch (fallo) { + apuntar( + ` ✓ ${nombre} — rechazado: ${fallo.message.split(String.fromCharCode(10))[0].slice(0, 70)}` + ); + return true; + } finally { + await otro.query('ROLLBACK').catch(() => {}); + await otro.end(); + } +} + +/** Y que lo legítimo sí pasa: una guarda que lo bloquea todo tampoco sirve. */ +async function permite(nombre, sql, valores = []) { + await c.query('SAVEPOINT prueba'); + try { + await c.query(sql, valores); + await c.query('RELEASE SAVEPOINT prueba'); + apuntar(` ✓ ${nombre} — permitido, como debe`); + return true; + } catch (fallo) { + await c.query('ROLLBACK TO SAVEPOINT prueba'); + apuntar(` ✗ ${nombre} — SE RECHAZÓ: ${fallo.message.split('\n')[0].slice(0, 70)}`); + return false; + } +} + +/* --- Datos mínimos para poder probar ---------------------------------- */ + +await c.query(`insert into pais (codigo, nombre) values ('CU', 'Cuba')`); +await c.query( + `insert into estilo (id, slug, nombre, principal) values ('e1', 'baladas', 'Baladas', true)` +); +await c.query( + `insert into cancion (id, slug, titulo, estilo_id) values ('c1', 'como-si-nada', 'Como si nada', 'e1')` +); +await c.query( + `insert into cancion (id, slug, titulo, estilo_id, version_de_id, version_nombre) + values ('c2', 'como-si-nada-maqueta', 'Como si nada', 'e1', 'c1', 'Maqueta 2018')` +); +await c.query(`insert into persona (id, slug, nombre) values ('p1', 'juan', 'Juan')`); +await c.query(`insert into genero (id, slug, nombre) values ('g1', 'bolero', 'Bolero')`); +await c.query(`insert into genero (id, slug, nombre) values ('g2', 'son', 'Son cubano')`); +await c.query(`insert into genero (id, slug, nombre) values ('g3', 'bolero-son', 'Bolero-son')`); + +apuntar('LAS VERSIONES'); +await permite( + 'una canción puede tener versiones', + `insert into cancion (id, slug, titulo, estilo_id, version_de_id) + values ('c3', 'orquestal', 'Como si nada', 'e1', 'c1')` +); +await rechaza( + 'una versión no puede tener versiones', + `insert into cancion (id, slug, titulo, estilo_id, version_de_id) + values ('c4', 'x', 'X', 'e1', 'c2')` +); +await rechaza( + 'una canción no es versión de sí misma', + `update cancion set version_de_id = 'c1' where id = 'c1'` +); + +apuntar('\nLA VISTA DE PRINCIPALES'); +const vista = await c.query('select count(*)::int n from cancion_principal'); +const todas = await c.query('select count(*)::int n from cancion'); +const principales = await c.query( + 'select count(*)::int n from cancion where version_de_id is null' +); +const bien = vista.rows[0].n === principales.rows[0].n && vista.rows[0].n < todas.rows[0].n; +apuntar( + ` ${bien ? '✓' : '✗'} cancion tiene ${todas.rows[0].n} filas y cancion_principal ${vista.rows[0].n}, que son las principales` +); + +apuntar('\nEL LINAJE DE GÉNEROS'); +await permite( + 'el bolero-son deriva de dos géneros', + `insert into genero_origen (genero_id, origen_id, tipo, principal) values + ('g3', 'g1', 'fusiona', true), ('g3', 'g2', 'fusiona', false)` +); +await rechaza( + 'un género no deriva de sí mismo', + `insert into genero_origen (genero_id, origen_id, tipo) values ('g1', 'g1', 'deriva')` +); +await rechaza( + 'no se admiten ciclos en el linaje', + `insert into genero_origen (genero_id, origen_id, tipo) values ('g1', 'g3', 'deriva')` +); +await rechaza( + 'solo una arista principal por género', + `update genero_origen set principal = true where genero_id = 'g3' and origen_id = 'g2'` +); + +apuntar('\nEL REPARTO DE AUTORÍA'); +await permite( + 'un reparto que suma cien pasa', + `insert into autoria (cancion_id, persona_id, rol, porcentaje) + values ('c1', 'p1', 'letra', 60), ('c1', 'p1', 'musica', 40)` +); +/* + * Sobre otra canción y con un rol libre, para que el rechazo venga de la suma y + * no de la clave primaria. La primera versión de esta prueba pasaba por el + * motivo equivocado —clave duplicada— y dejaba el disparador sin comprobar, que + * es peor que no tener prueba. + */ +await rechazaAlCerrar( + 'un reparto que suma noventa, no', + [ + `insert into estilo (id, slug, nombre) values ('z1', 'z', 'Z')`, + `insert into cancion (id, slug, titulo, estilo_id) values ('z2', 'z', 'Z', 'z1')`, + `insert into persona (id, slug, nombre) values ('z3', 'z', 'Z')` + ], + `insert into autoria (cancion_id, persona_id, rol, porcentaje) + values ('z2', 'z3', 'letra', 90)` +); +await permite( + 'y el de c3 sí, si llega a cien', + `insert into autoria (cancion_id, persona_id, rol, porcentaje) + values ('c3', 'p1', 'letra', 90), ('c3', 'p1', 'musica', 10)` +); + +apuntar('\nEL ESTILO PRINCIPAL'); +await rechaza( + 'no puede haber dos estilos principales', + `insert into estilo (id, slug, nombre, principal) values ('e2', 'rock', 'Rock', true)` +); +await permite( + 'los no principales, los que hagan falta', + `insert into estilo (id, slug, nombre) values ('e3', 'clasica', 'Clásica')` +); + +apuntar('\nLAS FACTURAS'); +await rechaza( + 'una rectificativa tiene que decir a cuál rectifica', + `insert into factura (id, pedido_id, serie, numero, tipo, base_centimos, iva_tipo, + iva_cuota_centimos, total_centimos, datos_congelados, pais_codigo) + values ('f1', 'inexistente', 'A', 1, 'rectificativa', 100, 21, 21, 121, '{}', 'CU')` +); + +apuntar('\nLA VALORACIÓN'); +await rechaza( + 'no se puede puntuar con un seis', + `insert into valoracion (usuario_id, cancion_slug, puntuacion) + values ('inexistente', 'x', 6)` +); + +await c.query('ROLLBACK'); +apuntar('\n(todo deshecho: la base queda como estaba)'); +await c.end(); diff --git a/scripts/db/probar-migracion.mjs b/scripts/db/probar-migracion.mjs new file mode 100644 index 0000000..3775be7 --- /dev/null +++ b/scripts/db/probar-migracion.mjs @@ -0,0 +1,74 @@ +/** + * Ejecuta la migración en una transacción y la deshace. + * + * `drizzle-kit migrate` se queda colgado sin decir por qué, así que hay que + * preguntarle directamente a Postgres. + * + * El progreso se escribe a disco de forma SÍNCRONA. La salida normal de Node + * se acumula en un buffer cuando va a un pipe o a un archivo, y si el proceso + * se mata por tiempo ese buffer se pierde entero: es justo lo que hacía que + * pareciera que no llegaba a arrancar. + */ +import fs from 'node:fs'; +import pg from 'pg'; + +const ARCHIVO = 'drizzle/0002_catalogo_en_la_base.sql'; +const DIARIO = '.diario-migracion.txt'; +const APLICAR = process.argv.includes('--aplicar'); + +fs.writeFileSync(DIARIO, ''); +const apuntar = (linea) => fs.appendFileSync(DIARIO, linea + '\n'); + +const sql = fs.readFileSync(ARCHIVO, 'utf8'); +const sentencias = sql + .split('--> statement-breakpoint') + .map((s) => s.trim()) + // Sin expresión regular. `(--[^\n]*\n?)+` tarda catorce segundos en un + // comentario de quinientos caracteres: cuando no encaja, puede repartir el + // mismo texto de exponencialmente muchas formas antes de rendirse. Línea a + // línea es lineal, y además se lee. + .filter((s) => s && s.split('\n').some((l) => l.trim() && !l.trim().startsWith('--'))); + +apuntar(`${sentencias.length} sentencias`); + +const cliente = new pg.Client({ + connectionString: process.env.DATABASE_URL, + // Que no se quede esperando eternamente si el servidor no contesta. + connectionTimeoutMillis: 10_000, + query_timeout: 30_000 +}); + +apuntar('conectando...'); +await cliente.connect(); +apuntar('conectado'); + +await cliente.query('BEGIN'); +// Que falle rápido en vez de colgarse esperando un bloqueo que no llega. +await cliente.query("SET LOCAL lock_timeout = '5s'"); +await cliente.query("SET LOCAL statement_timeout = '30s'"); +apuntar('transacción abierta'); + +let hechas = 0; +let rota = null; + +for (const [i, sentencia] of sentencias.entries()) { + const resumen = sentencia.split('\n').find((l) => l.trim() && !l.trim().startsWith('--')) ?? ''; + apuntar(`[${i + 1}/${sentencias.length}] ${resumen.trim().slice(0, 80)}`); + try { + await cliente.query(sentencia); + hechas++; + } catch (fallo) { + rota = { i, sentencia, fallo }; + apuntar(`\n FALLA: ${fallo.message}`); + if (fallo.hint) apuntar(` pista: ${fallo.hint}`); + if (fallo.position) apuntar(` posición: ${fallo.position}`); + apuntar('\n' + sentencia.slice(0, 600)); + break; + } +} + +const completa = !rota && hechas === sentencias.length; +await cliente.query(APLICAR && completa ? 'COMMIT' : 'ROLLBACK'); +apuntar(completa ? `\nLas ${hechas} sentencias pasan.` : `\nParó en la ${hechas + 1}.`); +apuntar(APLICAR && completa ? 'APLICADA.' : '(deshecha)'); +await cliente.end(); diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts deleted file mode 100644 index 0ff557c..0000000 --- a/src/lib/server/db/schema.ts +++ /dev/null @@ -1,347 +0,0 @@ -/** - * Esquema de la base de datos. - * - * Aqui no vive el catalogo: los estilos, albumes y canciones siguen siendo - * archivos Markdown del repositorio. La base de datos guarda solo lo que no - * puede estar en el codigo porque lo generan las personas que visitan la web: - * cuentas, carritos, pedidos y compras. - * - * Las canciones se referencian por su `slug`, no por una clave foranea: la - * fuente de verdad del catalogo es el contenido, y una compra tiene que - * sobrevivir aunque un tema se renombre o se retire de la web. - */ - -import { relations } from 'drizzle-orm'; -import { - bigint, - index, - integer, - pgTable, - primaryKey, - text, - timestamp, - uniqueIndex -} from 'drizzle-orm/pg-core'; - -const id = () => - text('id') - .primaryKey() - .$defaultFn(() => crypto.randomUUID()); - -/** - * Marca de tiempo obligatoria que se rellena sola al insertar. - * - * Con zona horaria siempre: el servidor puede estar en una y quien mira en - * otra, y un instante sin zona es un instante que no se sabe cual es. Y en - * modo `date` porque el codigo trabaja con `Date`; el modo por defecto de - * Postgres en Drizzle devuelve cadenas. - */ -const ahora = (columna: string) => - timestamp(columna, { withTimezone: true, mode: 'date' }) - .notNull() - .$defaultFn(() => new Date()); - -/** Instante opcional, con el mismo criterio. */ -const instante = (columna: string) => timestamp(columna, { withTimezone: true, mode: 'date' }); - -/* ------------------------------------------------------------------------- - * Cuentas - * ---------------------------------------------------------------------- */ - -export const usuario = pgTable('usuario', { - id: id(), - /** Siempre normalizado en minusculas y sin espacios. */ - email: text('email').notNull().unique(), - nombre: text('nombre'), - creadoEn: ahora('creado_en'), - ultimoAccesoEn: instante('ultimo_acceso_en') -}); - -/** - * Una cuenta de un proveedor externo enlazada a un usuario de aqui. - * - * El par (proveedor, sujeto) es la clave: `sujeto` es el identificador estable - * que da el proveedor —el `sub` de Google, el `id` de Facebook—, y NO el correo. - * Un correo se puede cambiar, y en algunos proveedores se puede reutilizar - * cuando se libera una cuenta; el identificador no. Enlazar por correo dejaria - * la puerta abierta a entrar en la cuenta de otro el dia que un proveedor - * reasignara una direccion. - * - * Aqui NO se guarda ningun token del proveedor. Se usa una vez, en el momento - * de entrar, para preguntar quien es, y se tira: no hacemos nada mas con esa - * cuenta —ni leer contactos, ni publicar— asi que guardarlo solo anadiria algo - * mas que puede filtrarse. - */ -export const identidad = pgTable( - 'identidad', - { - id: id(), - proveedor: text('proveedor', { enum: ['google', 'facebook'] }).notNull(), - /** Identificador de la persona en ese proveedor. Estable y opaco. */ - sujeto: text('sujeto').notNull(), - usuarioId: text('usuario_id') - .notNull() - .references(() => usuario.id, { onDelete: 'cascade' }), - /** El correo que dio el proveedor al enlazar. Informativo. */ - email: text('email'), - creadaEn: ahora('creada_en'), - ultimoAccesoEn: instante('ultimo_acceso_en') - }, - (t) => [ - // Una cuenta de Google no puede apuntar a dos usuarios de aqui. - uniqueIndex('identidad_proveedor_sujeto').on(t.proveedor, t.sujeto), - index('identidad_usuario').on(t.usuarioId) - ] -); - -/** - * Codigo de un solo uso enviado por correo. - * - * Se guarda el hash, no el codigo: quien lea la base de datos no debe poder - * entrar en una cuenta ajena. Es la misma razon por la que las sesiones - * guardan el hash del token. - */ -export const codigoAcceso = pgTable( - 'codigo_acceso', - { - id: id(), - email: text('email').notNull(), - hash: text('hash').notNull(), - expiraEn: instante('expira_en').notNull(), - intentos: integer('intentos').notNull().default(0), - usadoEn: instante('usado_en'), - creadoEn: ahora('creado_en') - }, - (t) => [index('codigo_acceso_email').on(t.email)] -); - -export const sesion = pgTable( - 'sesion', - { - /** Hash del token que viaja en la cookie. */ - id: text('id').primaryKey(), - usuarioId: text('usuario_id') - .notNull() - .references(() => usuario.id, { onDelete: 'cascade' }), - expiraEn: instante('expira_en').notNull(), - creadaEn: ahora('creada_en') - }, - (t) => [index('sesion_usuario').on(t.usuarioId)] -); - -/** - * Contadores del limitador de envios. - * - * Esta en la base de datos y no en memoria a proposito: un limite que se borra - * al reiniciar el proceso no frena a quien insiste, y sobre todo no protege a - * un tercero. Aqui el limite existe para que nadie pueda usar el formulario de - * acceso para bombardear el buzon de otra persona. - * - * `expiraEn` va en milisegundos y no como fecha porque las ventanas son - * aritmetica pura, y algunas duran menos de un minuto. - * - * Y va en `bigint`, no en `integer`: en SQLite un entero son ocho bytes, pero - * en Postgres son cuatro y el maximo se queda en 2.147.483.647. Un instante en - * milisegundos ronda el billon y siete, asi que en `integer` desbordaria y el - * limitador dejaria de limitar. - */ -export const limite = pgTable( - 'limite', - { - /** Que se esta limitando: "acceso:ip:1.2.3.4", "acceso:correo:..." */ - clave: text('clave').primaryKey(), - intentos: integer('intentos').notNull(), - expiraEn: bigint('expira_en', { mode: 'number' }).notNull() - }, - (t) => [index('limite_expira').on(t.expiraEn)] -); - -/* ------------------------------------------------------------------------- - * Carrito - * ---------------------------------------------------------------------- */ - -/** - * Un carrito existe antes que la cuenta: se puede ir llenando sin haber - * entrado, identificado por una cookie, y al iniciar sesion se fusiona con el - * del usuario. - */ -export const carrito = pgTable( - 'carrito', - { - id: id(), - usuarioId: text('usuario_id').references(() => usuario.id, { onDelete: 'cascade' }), - creadoEn: ahora('creado_en'), - actualizadoEn: ahora('actualizado_en') - }, - (t) => [index('carrito_usuario').on(t.usuarioId)] -); - -export const carritoItem = pgTable( - 'carrito_item', - { - carritoId: text('carrito_id') - .notNull() - .references(() => carrito.id, { onDelete: 'cascade' }), - cancionSlug: text('cancion_slug').notNull(), - anadidoEn: ahora('anadido_en') - }, - (t) => [primaryKey({ columns: [t.carritoId, t.cancionSlug] })] -); - -/* ------------------------------------------------------------------------- - * Pedidos y compras - * ---------------------------------------------------------------------- */ - -export const pedido = pgTable( - 'pedido', - { - id: id(), - usuarioId: text('usuario_id') - .notNull() - .references(() => usuario.id, { onDelete: 'cascade' }), - /** Identificador de la sesion de pago de Stripe. */ - sesionPagoId: text('sesion_pago_id').notNull().unique(), - estado: text('estado', { enum: ['pendiente', 'pagado', 'cancelado'] }) - .notNull() - .default('pendiente'), - /** Numero de packs de 5 canciones. */ - packs: integer('packs').notNull(), - /** Importe en centimos, congelado en el momento del pedido. */ - totalCentimos: integer('total_centimos').notNull(), - moneda: text('moneda').notNull().default('eur'), - creadoEn: ahora('creado_en'), - pagadoEn: instante('pagado_en') - }, - (t) => [index('pedido_usuario').on(t.usuarioId)] -); - -export const pedidoItem = pgTable( - 'pedido_item', - { - pedidoId: text('pedido_id') - .notNull() - .references(() => pedido.id, { onDelete: 'cascade' }), - cancionSlug: text('cancion_slug').notNull(), - /** Titulo en el momento de la compra, para que la factura no cambie. */ - titulo: text('titulo').notNull() - }, - (t) => [primaryKey({ columns: [t.pedidoId, t.cancionSlug] })] -); - -/** - * Lo que da derecho a descargar. Es una tabla aparte de `pedidoItem` a - * proposito: responde a "puede esta persona descargar este tema" en una sola - * consulta, sin recorrer pedidos. - */ -export const compra = pgTable( - 'compra', - { - usuarioId: text('usuario_id') - .notNull() - .references(() => usuario.id, { onDelete: 'cascade' }), - cancionSlug: text('cancion_slug').notNull(), - pedidoId: text('pedido_id') - .notNull() - .references(() => pedido.id, { onDelete: 'cascade' }), - compradoEn: ahora('comprado_en') - }, - (t) => [primaryKey({ columns: [t.usuarioId, t.cancionSlug] })] -); - -/* ------------------------------------------------------------------------- - * Favoritos - * ---------------------------------------------------------------------- */ - -/** - * Los temas que una cuenta ha marcado. - * - * Es una lista personal, no una compra ni un voto: se puede marcar cualquier - * tema, este a la venta o no, y no dice nada publico sobre el. Por eso no lleva - * ningun contador agregado. - * - * La clave primaria es el par cuenta-tema, asi que marcar dos veces lo mismo no - * duplica nada. El indice por cuenta es para pintar la lista, que es la - * consulta que se hace de verdad. - */ -export const favorito = pgTable( - 'favorito', - { - usuarioId: text('usuario_id') - .notNull() - .references(() => usuario.id, { onDelete: 'cascade' }), - cancionSlug: text('cancion_slug').notNull(), - marcadoEn: ahora('marcado_en') - }, - (t) => [ - primaryKey({ columns: [t.usuarioId, t.cancionSlug] }), - index('idx_favorito_usuario').on(t.usuarioId) - ] -); - -/* ------------------------------------------------------------------------- - * Valoraciones - * ---------------------------------------------------------------------- */ - -/** - * Lo que una cuenta le pone a un tema, de 1 a 5. - * - * La clave primaria es el par cuenta-tema, asi que nadie puede votar dos veces - * lo mismo: volver a votar reemplaza el voto anterior. Eso lo garantiza la base - * de datos, no el codigo de la accion, que es donde tiene que estar. - * - * El indice por `cancion_slug` es para la media: se calcula por tema, y sin el - * habria que recorrer la tabla entera en cada ficha. - */ -export const valoracion = pgTable( - 'valoracion', - { - usuarioId: text('usuario_id') - .notNull() - .references(() => usuario.id, { onDelete: 'cascade' }), - cancionSlug: text('cancion_slug').notNull(), - /** De 1 a 5. El rango lo comprueba tambien la aplicacion al guardar. */ - puntuacion: integer('puntuacion').notNull(), - creadaEn: ahora('creada_en'), - actualizadaEn: ahora('actualizada_en') - }, - (t) => [ - primaryKey({ columns: [t.usuarioId, t.cancionSlug] }), - index('idx_valoracion_cancion').on(t.cancionSlug) - ] -); - -/* ------------------------------------------------------------------------- - * Relaciones - * ---------------------------------------------------------------------- */ - -export const relacionesUsuario = relations(usuario, ({ many }) => ({ - sesiones: many(sesion), - pedidos: many(pedido), - compras: many(compra), - valoraciones: many(valoracion), - favoritos: many(favorito) -})); - -export const relacionesCarrito = relations(carrito, ({ many, one }) => ({ - items: many(carritoItem), - usuario: one(usuario, { fields: [carrito.usuarioId], references: [usuario.id] }) -})); - -export const relacionesCarritoItem = relations(carritoItem, ({ one }) => ({ - carrito: one(carrito, { fields: [carritoItem.carritoId], references: [carrito.id] }) -})); - -export const relacionesPedido = relations(pedido, ({ many, one }) => ({ - items: many(pedidoItem), - usuario: one(usuario, { fields: [pedido.usuarioId], references: [usuario.id] }) -})); - -export const relacionesPedidoItem = relations(pedidoItem, ({ one }) => ({ - pedido: one(pedido, { fields: [pedidoItem.pedidoId], references: [pedido.id] }) -})); - -export type Usuario = typeof usuario.$inferSelect; -export type Pedido = typeof pedido.$inferSelect; -export type Compra = typeof compra.$inferSelect; -export type Valoracion = typeof valoracion.$inferSelect; -export type Favorito = typeof favorito.$inferSelect; diff --git a/src/lib/server/db/schema/base.ts b/src/lib/server/db/schema/base.ts new file mode 100644 index 0000000..24be1b5 --- /dev/null +++ b/src/lib/server/db/schema/base.ts @@ -0,0 +1,118 @@ +/** + * Las tablas de las que cuelga todo lo demás: países, archivos y personas. + * + * Van juntas y primero porque no dependen de nadie y casi todo depende de + * ellas. Un país lo necesita un género para decir de dónde es y una factura + * para saber qué IVA lleva; un archivo lo necesitan una canción, una portada y + * el sonido de muestra de un instrumento; y una persona firma una canción, es + * figura de un género o ambas cosas. + */ + +import { boolean, char, index, integer, pgTable, text } from 'drizzle-orm/pg-core'; +import { ahora, estado, id, instante } from './comun'; + +/** + * Países, por su código ISO 3166-1 de dos letras. + * + * El código ES la clave: es estable, universal y ya lo conoce todo el mundo, + * así que no hace falta inventar otro. Es la única tabla del modelo donde el + * identificador legible sirve de clave primaria, y es porque este no lo + * elegimos nosotros ni lo vamos a cambiar. + */ +export const pais = pgTable('pais', { + codigo: char('codigo', { length: 2 }).primaryKey(), + nombre: text('nombre').notNull(), + /** «cubano». Se usa al redactar, no solo al listar. */ + gentilicio: text('gentilicio'), + /** caribe · andina · cono-sur · mesoamerica · iberica · otra */ + region: text('region') +}); + +/** + * Un archivo: audio, imagen, muestra o documento. + * + * Antes de esto, un audio era una cadena de texto y una portada una ruta bajo + * `/static`. Nada garantizaba que el archivo existiera, nada sabía cuánto + * pesaba, y para que la administración pueda subir audio hace falta que un + * archivo sea una fila con la que se pueda hablar. + */ +export const medio = pgTable( + 'medio', + { + id: id(), + /** audio · imagen · muestra · documento */ + clase: text('clase', { enum: ['audio', 'imagen', 'muestra', 'documento'] }).notNull(), + /** Ruta dentro del almacén. Sin barra inicial: no es una URL. */ + clave: text('clave').notNull().unique(), + + /** + * Si se puede pedir por URL sin más. + * + * Los fragmentos de muestra sí; el audio completo no, nunca. Hasta ahora + * eso lo garantizaba la carpeta en la que estaba el archivo —un directorio + * privado fuera de `build/`—, que es una garantía que depende de que nadie + * mueva nada. Aquí es un dato, y el que sirve los archivos lo comprueba. + * + * Con los medios en su propio dominio esto se vuelve más importante, no + * menos: allí la cookie de sesión no viaja, así que lo privado se entrega + * con URL firmada y lo público directo. + */ + publico: boolean('publico').notNull().default(false), + + mime: text('mime').notNull(), + bytes: integer('bytes').notNull(), + + /** Medida al subir con ffprobe, no escrita a mano. */ + duracionS: integer('duracion_s'), + /** De las imágenes: evita el salto de maquetación al cargar. */ + ancho: integer('ancho'), + alto: integer('alto'), + + /** + * Huella del contenido. Detecta el archivo repetido antes de guardarlo dos + * veces, y sirve para comprobar que lo que hay en el disco sigue siendo lo + * que se subió. + */ + sha256: text('sha256').notNull(), + + /** Descripción de la imagen, donde tiene que estar: con la imagen. */ + alt: text('alt'), + + creadoEn: ahora('creado_en') + }, + (t) => [index('medio_clase').on(t.clase), index('medio_sha').on(t.sha256)] +); + +/** + * Quien escribe, compone, canta, produce o es figura de un género. + * + * Una sola tabla para todas esas cosas, y no una de autores y otra de + * letristas históricos. Un letrista es una persona; con dos tablas, la misma + * acabaría existiendo dos veces con distinta grafía el día que se colabore con + * alguien que además sea figura de un género. + */ +export const persona = pgTable( + 'persona', + { + id: id(), + slug: text('slug').notNull().unique(), + /** Como se acredita. */ + nombre: text('nombre').notNull(), + /** Si difiere del anterior. Es el que va en el reparto de derechos. */ + nombreLegal: text('nombre_legal'), + /** Código de autor en la entidad de gestión. */ + ipi: text('ipi'), + + /* --- Biografía: para las figuras de un género ----------------------- */ + nacimiento: integer('nacimiento'), + defuncion: integer('defuncion'), + paisCodigo: char('pais_codigo', { length: 2 }).references(() => pais.codigo), + retratoId: text('retrato_id').references(() => medio.id, { onDelete: 'set null' }), + semblanza: text('semblanza'), + + estado: estado(), + creadoEn: ahora('creado_en'), + actualizadoEn: instante('actualizado_en') + }, + (t) => [index('persona_nombre').on(t.nombre), index('persona_pais').on(t.paisCodigo)] +); diff --git a/src/lib/server/db/schema/catalogo.ts b/src/lib/server/db/schema/catalogo.ts new file mode 100644 index 0000000..57902c8 --- /dev/null +++ b/src/lib/server/db/schema/catalogo.ts @@ -0,0 +1,353 @@ +/** + * El catálogo: estilos, discos, canciones y todo lo que cuelga de una canción. + * + * Hasta ahora esto vivía en `src/content/**` como Markdown con frontmatter. No + * eran documentos: una canción tenía veinte campos estructurados en YAML y la + * letra entera también, y el cuerpo del Markdown eran cuatro líneas de + * comentario. Eran registros disfrazados de documento. + */ + +import { sql } from 'drizzle-orm'; +import { + boolean, + char, + index, + integer, + numeric, + pgTable, + primaryKey, + text, + uniqueIndex +} from 'drizzle-orm/pg-core'; +import { ahora, estado, id, instante } from './comun'; +import { medio, pais, persona } from './base'; + +/** + * Una sección del catálogo: baladas románticas, clásica, rock, pop melódico. + * + * NO es lo mismo que un género del taller. Aquello —bolero, tango, cumbia— es + * música hispanoamericana descrita para poder escribirla; esto es cómo el + * titular ordena su propia obra. Compartían la palabra «estilo» y por eso + * compartían nombre en el código, que es de donde venía la confusión. + */ +export const estilo = pgTable( + 'estilo', + { + id: id(), + slug: text('slug').notNull().unique(), + nombre: text('nombre').notNull(), + resumen: text('resumen').notNull().default(''), + /** Menor, más arriba. */ + orden: integer('orden').notNull().default(0), + /** El estilo principal del artista. Solo puede haber uno: ver el índice. */ + principal: boolean('principal').notNull().default(false), + imagenId: text('imagen_id').references(() => medio.id, { onDelete: 'set null' }), + estado: estado(), + creadoEn: ahora('creado_en'), + actualizadoEn: instante('actualizado_en') + }, + (t) => [ + /* + * Índice parcial: solo entran las filas con `principal` a cierto, así que + * la unicidad se aplica únicamente entre ellas y las demás no estorban. + * Hasta ahora, que solo hubiera un estilo principal era un acuerdo escrito + * en un comentario; ahora es la base la que no deja poner dos. + */ + uniqueIndex('estilo_principal_unico') + .on(t.principal) + .where(sql`${t.principal}`), + index('estilo_orden').on(t.orden) + ] +); + +/** Formatos en los que existe una edición. */ +export const FORMATOS = ['digital', 'cd', 'vinilo', 'casete'] as const; + +export const album = pgTable( + 'album', + { + id: id(), + slug: text('slug').notNull().unique(), + titulo: text('titulo').notNull(), + estiloId: text('estilo_id') + .notNull() + .references(() => estilo.id), + tipo: text('tipo', { enum: ['album', 'ep', 'single', 'coleccion'] }) + .notNull() + .default('album'), + /** Fecha de publicación, ISO `YYYY-MM-DD`. */ + fecha: text('fecha').notNull(), + portadaId: text('portada_id').references(() => medio.id, { onDelete: 'set null' }), + sello: text('sello'), + resumen: text('resumen').notNull().default(''), + destacado: boolean('destacado').notNull().default(false), + + /* --- Datos de edición: lo que iría en la contraportada --------------- */ + numeroCatalogo: text('numero_catalogo'), + /** Se pintan, no se filtran: array y no tabla puente. */ + formatos: text('formatos').array().$type<(typeof FORMATOS)[number][]>(), + paisEdicionCodigo: char('pais_edicion_codigo', { length: 2 }).references(() => pais.codigo), + + estado: estado(), + creadoEn: ahora('creado_en'), + actualizadoEn: instante('actualizado_en') + }, + (t) => [index('album_estilo').on(t.estiloId), index('album_fecha').on(t.fecha)] +); + +/** + * Un tema. Y también cada una de sus versiones. + * + * **Una versión es una fila de esta misma tabla**, con `versionDeId` apuntando + * a la principal. No es una tabla aparte, y no es un `obra`/`grabacion` partido + * en dos: en ese reparto el estilo y las etiquetas colgarían de la obra, y una + * versión no podría tener los suyos —una balada con arreglo de bolero no + * cabría—. Aquí cualquier campo puede cambiar de una versión a otra. + * + * Eso arregla de paso algo que estaba mal: **el disco es de la fila**. Antes + * `album` y `numero` colgaban del tema, así que la «Maqueta 2018» era el corte + * 1 del álbum igual que la versión de estudio, y es falso. + * + * Al crear una versión sus campos se **copian** de la principal; no se heredan + * al leer. Un nulo heredado nunca se sabe si significa «no lo sé» o «lo mismo + * de arriba». Y aquí copiar es además lo correcto: la maqueta de 2018 no tiene + * la misma letra, le falta el puente. + */ +export const cancion = pgTable( + 'cancion', + { + id: id(), + slug: text('slug').notNull().unique(), + titulo: text('titulo').notNull(), + + /** + * Nula en las principales. Una versión apunta aquí a su tema de + * referencia. + * + * La clave foránea se declara en la migración y no aquí: Drizzle no deja + * referenciar una tabla dentro de su propia definición. Allí va también la + * guarda que impide la versión de una versión y los ciclos. + * + * Ojo al consultar: casi todo lo que lista o cuenta canciones quiere solo + * las principales. Para eso está la vista `cancion_principal`; pedir + * versiones tiene que ser lo que se escribe a propósito. + */ + versionDeId: text('version_de_id'), + /** Nombre del arreglo: «Versión orquestal», «Maqueta 2018». */ + versionNombre: text('version_nombre'), + /** Orden entre las versiones de un mismo tema. */ + versionOrden: integer('version_orden').notNull().default(0), + + estiloId: text('estilo_id') + .notNull() + .references(() => estilo.id), + albumId: text('album_id').references(() => album.id, { onDelete: 'set null' }), + numero: integer('numero'), + + audioId: text('audio_id').references(() => medio.id, { onDelete: 'set null' }), + muestraId: text('muestra_id').references(() => medio.id, { onDelete: 'set null' }), + duracionS: integer('duracion_s'), + ano: integer('ano'), + + /** Con sus saltos de línea: es verso, no prosa. */ + letra: text('letra'), + /** ISO 639. Decide si la ficha enseña una columna o dos. */ + idioma: text('idioma').notNull().default('es'), + /** El cuerpo del Markdown de antes. */ + nota: text('nota'), + inspiracion: text('inspiracion'), + + instrumental: boolean('instrumental').notNull().default(false), + destacada: boolean('destacada').notNull().default(false), + portadaId: text('portada_id').references(() => medio.id, { onDelete: 'set null' }), + + /** Solo en las principales: comprar una entrega todas sus versiones. */ + alaVenta: boolean('ala_venta').notNull().default(false), + + estado: estado(), + creadoEn: ahora('creado_en'), + actualizadoEn: instante('actualizado_en') + }, + (t) => [ + index('cancion_estilo').on(t.estiloId), + index('cancion_album').on(t.albumId), + index('cancion_version_de').on(t.versionDeId), + index('cancion_destacada').on(t.destacada) + ] +); + +/** La letra en otro idioma. Antes solo cabía una, al español. */ +export const traduccion = pgTable( + 'traduccion', + { + cancionId: text('cancion_id') + .notNull() + .references(() => cancion.id, { onDelete: 'cascade' }), + idioma: text('idioma').notNull(), + texto: text('texto').notNull() + }, + (t) => [primaryKey({ columns: [t.cancionId, t.idioma] })] +); + +/** Quién hizo qué en un tema. El orden en que se acredita importa. */ +export const cancionPersona = pgTable( + 'cancion_persona', + { + cancionId: text('cancion_id') + .notNull() + .references(() => cancion.id, { onDelete: 'cascade' }), + personaId: text('persona_id') + .notNull() + .references(() => persona.id), + rol: text('rol', { + enum: ['letra', 'musica', 'interprete', 'arreglos', 'produccion'] + }).notNull(), + orden: integer('orden').notNull().default(0) + }, + (t) => [primaryKey({ columns: [t.cancionId, t.personaId, t.rol] })] +); + +/** + * Registro de la obra ante una entidad de gestión. + * + * El `iswc` identifica la COMPOSICIÓN, así que se repite entre un tema y sus + * versiones: es la misma obra. El `isrc` identifica una GRABACIÓN, y por eso va + * en la fila de cada versión. Tenerlos juntos en la canción era un fallo del + * modelo anterior, que se veía en cuanto un tema tenía dos grabaciones. + */ +export const registroObra = pgTable('registro_obra', { + cancionId: text('cancion_id') + .primaryKey() + .references(() => cancion.id, { onDelete: 'cascade' }), + iswc: text('iswc'), + isrc: text('isrc'), + entidad: text('entidad'), + numero: text('numero'), + /** No es lo mismo que el sello discográfico. */ + editorial: text('editorial') +}); + +/** + * El reparto de autoría, que es como lo liquidan las entidades de gestión. + * + * `numeric(5,2)` y no entero: los repartos reales llevan decimales. Y suma 100 + * por obra, comprobado con una restricción en la migración y no solo al validar + * el formulario, porque un reparto que no suma cien no es un dato incompleto: + * es dinero mal repartido. + */ +export const autoria = pgTable( + 'autoria', + { + cancionId: text('cancion_id') + .notNull() + .references(() => cancion.id, { onDelete: 'cascade' }), + personaId: text('persona_id') + .notNull() + .references(() => persona.id), + rol: text('rol', { enum: ['letra', 'musica', 'letra y musica'] }).notNull(), + porcentaje: numeric('porcentaje', { precision: 5, scale: 2 }).notNull() + }, + (t) => [primaryKey({ columns: [t.cancionId, t.personaId, t.rol] })] +); + +/** Un vídeo del tema en YouTube. Se guarda el id, no la URL: la URL cambia. */ +export const video = pgTable( + 'video', + { + id: id(), + cancionId: text('cancion_id') + .notNull() + .references(() => cancion.id, { onDelete: 'cascade' }), + youtubeId: text('youtube_id').notNull(), + titulo: text('titulo').notNull(), + orden: integer('orden').notNull().default(0) + }, + (t) => [index('video_cancion').on(t.cancionId)] +); + +export const PLATAFORMAS = [ + 'spotify', + 'appleMusic', + 'youtube', + 'youtubeMusic', + 'bandcamp', + 'soundcloud', + 'deezer', + 'tidal', + 'amazonMusic' +] as const; + +/* + * Dos tablas de enlaces y no una polimórfica con (tipo, id): una clave foránea + * de verdad vale más que ahorrarse una tabla. + */ +export const cancionEnlace = pgTable( + 'cancion_enlace', + { + cancionId: text('cancion_id') + .notNull() + .references(() => cancion.id, { onDelete: 'cascade' }), + plataforma: text('plataforma', { enum: PLATAFORMAS }).notNull(), + url: text('url').notNull() + }, + (t) => [primaryKey({ columns: [t.cancionId, t.plataforma] })] +); + +export const albumEnlace = pgTable( + 'album_enlace', + { + albumId: text('album_id') + .notNull() + .references(() => album.id, { onDelete: 'cascade' }), + plataforma: text('plataforma', { enum: PLATAFORMAS }).notNull(), + url: text('url').notNull() + }, + (t) => [primaryKey({ columns: [t.albumId, t.plataforma] })] +); + +/** Alguien acreditado en la edición de un disco: producción, portada, mezcla. */ +export const creditoAlbum = pgTable( + 'credito_album', + { + id: id(), + albumId: text('album_id') + .notNull() + .references(() => album.id, { onDelete: 'cascade' }), + personaId: text('persona_id').references(() => persona.id), + /** Si no hay ficha de persona, al menos el nombre. */ + nombre: text('nombre'), + rol: text('rol').notNull(), + orden: integer('orden').notNull().default(0) + }, + (t) => [index('credito_album_album').on(t.albumId)] +); + +/** + * Etiquetas: lo que cruza el catálogo por donde no lo hacen el estilo ni el + * disco. + * + * Antes se deducían del texto libre de cada archivo, así que «desamor» y + * «Desamor» eran dos etiquetas distintas y nadie lo veía. Ahora el vocabulario + * es una tabla, y lo comparten las canciones y la temática de los géneros: eso + * es lo que permite cruzar «el desamor: nueve temas míos y cuatro géneros que + * viven de él». + */ +export const etiqueta = pgTable('etiqueta', { + id: id(), + slug: text('slug').notNull().unique(), + /** Como se escribió la primera vez; es lo que se lee en pantalla. */ + nombre: text('nombre').notNull() +}); + +export const cancionEtiqueta = pgTable( + 'cancion_etiqueta', + { + cancionId: text('cancion_id') + .notNull() + .references(() => cancion.id, { onDelete: 'cascade' }), + etiquetaId: text('etiqueta_id') + .notNull() + .references(() => etiqueta.id, { onDelete: 'cascade' }) + }, + (t) => [primaryKey({ columns: [t.cancionId, t.etiquetaId] })] +); diff --git a/src/lib/server/db/schema/comercio.ts b/src/lib/server/db/schema/comercio.ts new file mode 100644 index 0000000..867c650 --- /dev/null +++ b/src/lib/server/db/schema/comercio.ts @@ -0,0 +1,104 @@ +/** + * Carrito, pedidos y compras. + * + * Aquí es donde más se nota la migración del contenido: estas tablas + * apuntaban a una canción por una cadena de texto, sin clave foránea, porque + * el destino vivía en un archivo. Renombrar un slug dejaba huérfana una compra + * sin que saltara ningún error. + */ + +import { index, integer, pgTable, primaryKey, text } from 'drizzle-orm/pg-core'; +import { ahora, id, instante } from './comun'; +import { usuario } from './cuentas'; + +/* ------------------------------------------------------------------------- + * Carrito + * ---------------------------------------------------------------------- */ + +/** + * Un carrito existe antes que la cuenta: se puede ir llenando sin haber + * entrado, identificado por una cookie, y al iniciar sesion se fusiona con el + * del usuario. + */ +export const carrito = pgTable( + 'carrito', + { + id: id(), + usuarioId: text('usuario_id').references(() => usuario.id, { onDelete: 'cascade' }), + creadoEn: ahora('creado_en'), + actualizadoEn: ahora('actualizado_en') + }, + (t) => [index('carrito_usuario').on(t.usuarioId)] +); + +export const carritoItem = pgTable( + 'carrito_item', + { + carritoId: text('carrito_id') + .notNull() + .references(() => carrito.id, { onDelete: 'cascade' }), + cancionSlug: text('cancion_slug').notNull(), + anadidoEn: ahora('anadido_en') + }, + (t) => [primaryKey({ columns: [t.carritoId, t.cancionSlug] })] +); + +/* ------------------------------------------------------------------------- + * Pedidos y compras + * ---------------------------------------------------------------------- */ + +export const pedido = pgTable( + 'pedido', + { + id: id(), + usuarioId: text('usuario_id') + .notNull() + .references(() => usuario.id, { onDelete: 'cascade' }), + /** Identificador de la sesion de pago de Stripe. */ + sesionPagoId: text('sesion_pago_id').notNull().unique(), + estado: text('estado', { enum: ['pendiente', 'pagado', 'cancelado'] }) + .notNull() + .default('pendiente'), + /** Numero de packs de 5 canciones. */ + packs: integer('packs').notNull(), + /** Importe en centimos, congelado en el momento del pedido. */ + totalCentimos: integer('total_centimos').notNull(), + moneda: text('moneda').notNull().default('eur'), + creadoEn: ahora('creado_en'), + pagadoEn: instante('pagado_en') + }, + (t) => [index('pedido_usuario').on(t.usuarioId)] +); + +export const pedidoItem = pgTable( + 'pedido_item', + { + pedidoId: text('pedido_id') + .notNull() + .references(() => pedido.id, { onDelete: 'cascade' }), + cancionSlug: text('cancion_slug').notNull(), + /** Titulo en el momento de la compra, para que la factura no cambie. */ + titulo: text('titulo').notNull() + }, + (t) => [primaryKey({ columns: [t.pedidoId, t.cancionSlug] })] +); + +/** + * Lo que da derecho a descargar. Es una tabla aparte de `pedidoItem` a + * proposito: responde a "puede esta persona descargar este tema" en una sola + * consulta, sin recorrer pedidos. + */ +export const compra = pgTable( + 'compra', + { + usuarioId: text('usuario_id') + .notNull() + .references(() => usuario.id, { onDelete: 'cascade' }), + cancionSlug: text('cancion_slug').notNull(), + pedidoId: text('pedido_id') + .notNull() + .references(() => pedido.id, { onDelete: 'cascade' }), + compradoEn: ahora('comprado_en') + }, + (t) => [primaryKey({ columns: [t.usuarioId, t.cancionSlug] })] +); diff --git a/src/lib/server/db/schema/comun.ts b/src/lib/server/db/schema/comun.ts new file mode 100644 index 0000000..fdc4b37 --- /dev/null +++ b/src/lib/server/db/schema/comun.ts @@ -0,0 +1,76 @@ +/** + * Piezas que se repiten en todas las tablas. + * + * Están aquí y no copiadas en cada archivo porque son decisiones, no atajos: + * qué forma tiene una clave, con qué precisión se guarda un instante y qué + * estados admite algo publicable. Cambiarlas debe ser un sitio, no cuarenta. + */ + +import { text, timestamp } from 'drizzle-orm/pg-core'; + +/** + * Clave primaria opaca. + * + * No se usa el slug: un slug es texto que se lee y que algún día se querrá + * corregir, y una clave primaria no debería cambiar nunca. El día que un tema + * se renombre, su identidad sigue siendo la misma. + */ +export const id = () => + text('id') + .primaryKey() + .$defaultFn(() => crypto.randomUUID()); + +/** + * Marca de tiempo obligatoria que se rellena sola al insertar. + * + * Con zona horaria siempre: el servidor puede estar en una y quien mira en + * otra, y un instante sin zona es un instante que no se sabe cuál es. Y en + * modo `date` porque el código trabaja con `Date`; el modo por defecto de + * Postgres en Drizzle devuelve cadenas. + * + * `defaultNow()` y no `$defaultFn(() => new Date())`, que era lo que había: lo + * segundo pone el valor desde JavaScript, así que la columna queda obligatoria + * **sin valor por defecto en la base**. Un `INSERT` escrito a mano falla, y la + * invariante que la tabla dice garantizar en realidad la sostenía el código de + * la aplicación. Se vio al probar las guardas con SQL directo. + */ +export const ahora = (columna: string) => + timestamp(columna, { withTimezone: true, mode: 'date' }).notNull().defaultNow(); + +/** Instante opcional, con el mismo criterio. */ +export const instante = (columna: string) => + timestamp(columna, { withTimezone: true, mode: 'date' }); + +/** + * Estado de publicación de cualquier contenido. + * + * `borrador` por defecto y no `publicado`: escribir una ficha a medias no puede + * significar publicarla, y el error de que algo salga antes de tiempo es peor + * que el de que tarde en salir. + * + * `retirado` existe para lo que no se puede borrar. Una canción que alguien ha + * comprado tiene que seguir existiendo aunque desaparezca del catálogo: la + * compra apunta a ella. + */ +export const ESTADOS = ['borrador', 'publicado', 'retirado'] as const; + +export const estado = () => text('estado', { enum: ESTADOS }).notNull().default('borrador'); + +/** + * De dónde salió una fila. + * + * Las fichas de género se generan fuera con un modelo grande y entran en JSON. + * Esto es lo que distingue lo que escribió una persona de lo que propuso una + * máquina, y lo que hace visible lo que la máquina se inventó por el camino: + * musicología publicada con el nombre del titular no puede depender de que + * alguien se acuerde de cuál era cuál. + */ +export const ORIGENES = ['manual', 'importado'] as const; + +export const procedencia = () => ({ + origen: text('origen', { enum: ORIGENES }).notNull().default('manual'), + modeloOrigen: text('modelo_origen'), + importadoEn: instante('importado_en'), + revisadoPor: text('revisado_por'), + revisadoEn: instante('revisado_en') +}); diff --git a/src/lib/server/db/schema/cuentas.ts b/src/lib/server/db/schema/cuentas.ts new file mode 100644 index 0000000..ddb2e25 --- /dev/null +++ b/src/lib/server/db/schema/cuentas.ts @@ -0,0 +1,121 @@ +/** + * Cuentas, sesiones y el freno de los envíos. + * + * Es la parte que ya estaba modelada como toca antes de la migración del + * contenido, y no cambia con ella. + */ + +import { index, integer, pgTable, text, uniqueIndex, bigint } from 'drizzle-orm/pg-core'; +import { ahora, id, instante } from './comun'; + +/* ------------------------------------------------------------------------- + * Cuentas + * ---------------------------------------------------------------------- */ + +export const usuario = pgTable('usuario', { + id: id(), + /** Siempre normalizado en minusculas y sin espacios. */ + email: text('email').notNull().unique(), + nombre: text('nombre'), + creadoEn: ahora('creado_en'), + ultimoAccesoEn: instante('ultimo_acceso_en') +}); + +/** + * Una cuenta de un proveedor externo enlazada a un usuario de aqui. + * + * El par (proveedor, sujeto) es la clave: `sujeto` es el identificador estable + * que da el proveedor —el `sub` de Google, el `id` de Facebook—, y NO el correo. + * Un correo se puede cambiar, y en algunos proveedores se puede reutilizar + * cuando se libera una cuenta; el identificador no. Enlazar por correo dejaria + * la puerta abierta a entrar en la cuenta de otro el dia que un proveedor + * reasignara una direccion. + * + * Aqui NO se guarda ningun token del proveedor. Se usa una vez, en el momento + * de entrar, para preguntar quien es, y se tira: no hacemos nada mas con esa + * cuenta —ni leer contactos, ni publicar— asi que guardarlo solo anadiria algo + * mas que puede filtrarse. + */ +export const identidad = pgTable( + 'identidad', + { + id: id(), + proveedor: text('proveedor', { enum: ['google', 'facebook'] }).notNull(), + /** Identificador de la persona en ese proveedor. Estable y opaco. */ + sujeto: text('sujeto').notNull(), + usuarioId: text('usuario_id') + .notNull() + .references(() => usuario.id, { onDelete: 'cascade' }), + /** El correo que dio el proveedor al enlazar. Informativo. */ + email: text('email'), + creadaEn: ahora('creada_en'), + ultimoAccesoEn: instante('ultimo_acceso_en') + }, + (t) => [ + // Una cuenta de Google no puede apuntar a dos usuarios de aqui. + uniqueIndex('identidad_proveedor_sujeto').on(t.proveedor, t.sujeto), + index('identidad_usuario').on(t.usuarioId) + ] +); + +/** + * Codigo de un solo uso enviado por correo. + * + * Se guarda el hash, no el codigo: quien lea la base de datos no debe poder + * entrar en una cuenta ajena. Es la misma razon por la que las sesiones + * guardan el hash del token. + */ +export const codigoAcceso = pgTable( + 'codigo_acceso', + { + id: id(), + email: text('email').notNull(), + hash: text('hash').notNull(), + expiraEn: instante('expira_en').notNull(), + intentos: integer('intentos').notNull().default(0), + usadoEn: instante('usado_en'), + creadoEn: ahora('creado_en') + }, + (t) => [index('codigo_acceso_email').on(t.email)] +); + +export const sesion = pgTable( + 'sesion', + { + /** Hash del token que viaja en la cookie. */ + id: text('id').primaryKey(), + usuarioId: text('usuario_id') + .notNull() + .references(() => usuario.id, { onDelete: 'cascade' }), + expiraEn: instante('expira_en').notNull(), + creadaEn: ahora('creada_en') + }, + (t) => [index('sesion_usuario').on(t.usuarioId)] +); + +/** + * Contadores del limitador de envios. + * + * Esta en la base de datos y no en memoria a proposito: un limite que se borra + * al reiniciar el proceso no frena a quien insiste, y sobre todo no protege a + * un tercero. Aqui el limite existe para que nadie pueda usar el formulario de + * acceso para bombardear el buzon de otra persona. + * + * `expiraEn` va en milisegundos y no como fecha porque las ventanas son + * aritmetica pura, y algunas duran menos de un minuto. + * + * Y va en `bigint`, no en `integer`: en SQLite un entero son ocho bytes, pero + * en Postgres son cuatro y el maximo se queda en 2.147.483.647. Un instante en + * milisegundos ronda el billon y siete, asi que en `integer` desbordaria y el + * limitador dejaria de limitar. + */ +export const limite = pgTable( + 'limite', + { + /** Que se esta limitando: "acceso:ip:1.2.3.4", "acceso:correo:..." */ + clave: text('clave').primaryKey(), + intentos: integer('intentos').notNull(), + expiraEn: bigint('expira_en', { mode: 'number' }).notNull() + }, + (t) => [index('limite_expira').on(t.expiraEn)] +); diff --git a/src/lib/server/db/schema/editorial.ts b/src/lib/server/db/schema/editorial.ts new file mode 100644 index 0000000..78b63bf --- /dev/null +++ b/src/lib/server/db/schema/editorial.ts @@ -0,0 +1,137 @@ +/** + * Blog, páginas sueltas, textos legales y las direcciones que ya no existen. + */ + +import { boolean, index, integer, pgTable, primaryKey, text } from 'drizzle-orm/pg-core'; +import { ahora, estado, id, instante } from './comun'; +import { medio } from './base'; +import { cancion, etiqueta } from './catalogo'; + +/** + * Una entrada del blog. Lo único del sitio que se fecha: el catálogo no caduca, + * pero un apunte sobre cómo se escribió un tema pertenece al día en que se + * escribió. + */ +export const entrada = pgTable( + 'entrada', + { + id: id(), + slug: text('slug').notNull().unique(), + titulo: text('titulo').notNull(), + /** ISO `YYYY-MM-DD`. */ + fecha: text('fecha').notNull(), + resumen: text('resumen').notNull().default(''), + cuerpo: text('cuerpo').notNull().default(''), + imagenId: text('imagen_id').references(() => medio.id, { onDelete: 'set null' }), + /** Sustituye al `borrador` de antes, que era un booleano suelto. */ + estado: estado(), + creadoEn: ahora('creado_en'), + actualizadoEn: instante('actualizado_en') + }, + (t) => [index('entrada_fecha').on(t.fecha)] +); + +/** + * De qué temas habla una entrada. + * + * Antes era una lista de slugs sin comprobar: si el tema no existía, la entrada + * enlazaba a un 404 y nadie se enteraba hasta que alguien pinchaba. + */ +export const entradaCancion = pgTable( + 'entrada_cancion', + { + entradaId: text('entrada_id') + .notNull() + .references(() => entrada.id, { onDelete: 'cascade' }), + cancionId: text('cancion_id') + .notNull() + .references(() => cancion.id, { onDelete: 'cascade' }) + }, + (t) => [primaryKey({ columns: [t.entradaId, t.cancionId] })] +); + +export const entradaEtiqueta = pgTable( + 'entrada_etiqueta', + { + entradaId: text('entrada_id') + .notNull() + .references(() => entrada.id, { onDelete: 'cascade' }), + etiquetaId: text('etiqueta_id') + .notNull() + .references(() => etiqueta.id, { onDelete: 'cascade' }) + }, + (t) => [primaryKey({ columns: [t.entradaId, t.etiquetaId] })] +); + +/** Texto libre: la biografía y lo que venga. */ +export const pagina = pgTable('pagina', { + id: id(), + slug: text('slug').notNull().unique(), + titulo: text('titulo').notNull(), + descripcion: text('descripcion').notNull().default(''), + cuerpo: text('cuerpo').notNull().default(''), + estado: estado(), + creadoEn: ahora('creado_en'), + actualizadoEn: instante('actualizado_en') +}); + +/** + * Aviso legal, condiciones, privacidad, cookies, condiciones de venta. + * + * El texto no vive aquí: vive en sus versiones. Esta tabla dice cuál está en + * vigor. + */ +export const documentoLegal = pgTable('documento_legal', { + id: id(), + slug: text('slug').notNull().unique(), + titulo: text('titulo').notNull(), + resumen: text('resumen').notNull().default(''), + orden: integer('orden').notNull().default(0), + /** Apunta a la versión vigente. La clave foránea va en la migración: + las dos tablas se referencian mutuamente. */ + versionVigenteId: text('version_vigente_id') +}); + +/** + * El historial de un documento legal. + * + * No es burocracia: es prueba. Quien compró en marzo se acogió a las + * condiciones de marzo, y si hay una reclamación hay que poder demostrar qué + * decían entonces, no qué dicen ahora. Una versión, una vez publicada, no se + * toca. + */ +export const versionLegal = pgTable( + 'version_legal', + { + id: id(), + documentoId: text('documento_id') + .notNull() + .references(() => documentoLegal.id, { onDelete: 'cascade' }), + cuerpo: text('cuerpo').notNull(), + vigenteDesde: ahora('vigente_desde'), + /** Qué cambió respecto de la anterior. */ + resumenCambios: text('resumen_cambios') + }, + (t) => [index('version_legal_documento').on(t.documentoId, t.vigenteDesde)] +); + +/** + * Direcciones que ya no existen y a dónde llevan ahora. + * + * Es lo que hace que renombrar un slug deje de romper enlaces publicados. Sin + * esto, corregir el nombre de un tema significa que cualquiera que lo hubiera + * compartido acaba en un 404, y por eso los slugs no se corrigen nunca aunque + * estén mal. + */ +export const redireccion = pgTable( + 'redireccion', + { + desde: text('desde').primaryKey(), + hacia: text('hacia').notNull(), + /** 301 permanente por defecto; 302 si algún día hace falta temporal. */ + permanente: boolean('permanente').notNull().default(true), + motivo: text('motivo'), + creadaEn: ahora('creada_en') + }, + (t) => [index('redireccion_hacia').on(t.hacia)] +); diff --git a/src/lib/server/db/schema/facturacion.ts b/src/lib/server/db/schema/facturacion.ts new file mode 100644 index 0000000..3504f69 --- /dev/null +++ b/src/lib/server/db/schema/facturacion.ts @@ -0,0 +1,136 @@ +/** + * Facturación: datos fiscales, facturas y el registro de entregas. + * + * Esto no existía. Hasta ahora se cobraba con Stripe y se daba acceso a la + * descarga, pero no se emitía nada; y vender exige facturar. + */ + +import { + boolean, + char, + index, + integer, + jsonb, + pgTable, + text, + uniqueIndex +} from 'drizzle-orm/pg-core'; +import { ahora, id, instante } from './comun'; +import { pais } from './base'; +import { usuario } from './cuentas'; +import { pedido } from './comercio'; + +/** + * Los datos fiscales de alguien. Puede tener varios y uno predeterminado. + * + * **El país decide el IVA, y no es el nuestro.** Una venta digital a un + * consumidor de otro país de la UE lleva el IVA de *su* país; fuera de la UE, + * normalmente ninguno. Por eso el tipo se guarda por línea de factura y no como + * una constante del sitio. + */ +export const datosFacturacion = pgTable( + 'datos_facturacion', + { + id: id(), + usuarioId: text('usuario_id') + .notNull() + .references(() => usuario.id, { onDelete: 'cascade' }), + tipo: text('tipo', { enum: ['particular', 'empresa'] }) + .notNull() + .default('particular'), + nombre: text('nombre').notNull(), + nif: text('nif'), + direccion: text('direccion'), + cp: text('cp'), + ciudad: text('ciudad'), + provincia: text('provincia'), + paisCodigo: char('pais_codigo', { length: 2 }) + .notNull() + .references(() => pais.codigo), + predeterminada: boolean('predeterminada').notNull().default(false), + creadoEn: ahora('creado_en'), + actualizadoEn: instante('actualizado_en') + }, + (t) => [index('datos_facturacion_usuario').on(t.usuarioId)] +); + +/** + * El contador de cada serie de facturación. + * + * **El correlativo no puede salir de `max(numero) + 1`.** Dos compras a la vez + * leerían el mismo máximo y emitirían el mismo número, y eso en una factura no + * es un fallo de programación: es un problema con Hacienda. Se toma bloqueando + * esta fila, que serializa las emisiones de una serie sin bloquear nada más. + */ +export const contadorFactura = pgTable('contador_factura', { + serie: text('serie').primaryKey(), + ultimoNumero: integer('ultimo_numero').notNull().default(0), + actualizadoEn: ahora('actualizado_en') +}); + +/** + * Una factura emitida. + * + * **Una factura emitida no se toca nunca.** Si una venta se anula, no se borra + * ni se corrige: se emite una rectificativa que apunta a ella. Por eso hay + * `tipo` y `rectificaAId`, y por eso los datos del cliente van congelados: si + * mañana cambia su dirección, la factura de ayer no puede cambiar con él. + */ +export const factura = pgTable( + 'factura', + { + id: id(), + pedidoId: text('pedido_id') + .notNull() + .references(() => pedido.id), + serie: text('serie').notNull(), + numero: integer('numero').notNull(), + tipo: text('tipo', { enum: ['normal', 'rectificativa'] }) + .notNull() + .default('normal'), + rectificaAId: text('rectifica_a_id'), + + fecha: ahora('fecha'), + + /* Todo en céntimos y congelado en el momento de emitir. */ + baseCentimos: integer('base_centimos').notNull(), + ivaTipo: integer('iva_tipo').notNull(), + ivaCuotaCentimos: integer('iva_cuota_centimos').notNull(), + totalCentimos: integer('total_centimos').notNull(), + moneda: text('moneda').notNull().default('eur'), + + /** Nombre, NIF y dirección tal como estaban ese día. */ + datosCongelados: jsonb('datos_congelados').notNull(), + /** El país que decidió el tipo, congelado también. */ + paisCodigo: char('pais_codigo', { length: 2 }) + .notNull() + .references(() => pais.codigo) + }, + (t) => [ + uniqueIndex('factura_serie_numero').on(t.serie, t.numero), + index('factura_pedido').on(t.pedidoId), + index('factura_fecha').on(t.fecha) + ] +); + +/** + * Cada entrega de un archivo a quien lo compró. + * + * Sirve para dos cosas concretas: poder demostrar la entrega si hay una + * reclamación, y ver si una cuenta se está compartiendo. + */ +export const descarga = pgTable( + 'descarga', + { + id: id(), + usuarioId: text('usuario_id') + .notNull() + .references(() => usuario.id, { onDelete: 'cascade' }), + /** La clave foránea a `cancion` entra en la fase 3, con las demás. */ + cancionSlug: text('cancion_slug').notNull(), + bajadaEn: ahora('bajada_en'), + ip: text('ip'), + agente: text('agente') + }, + (t) => [index('descarga_usuario').on(t.usuarioId), index('descarga_fecha').on(t.bajadaEn)] +); diff --git a/src/lib/server/db/schema/index.ts b/src/lib/server/db/schema/index.ts new file mode 100644 index 0000000..dd180fa --- /dev/null +++ b/src/lib/server/db/schema/index.ts @@ -0,0 +1,19 @@ +/** + * El esquema entero, en un solo sitio. + * + * Era un archivo y ahora es una carpeta: con el catálogo dentro de la base, un + * único archivo pasaría de las dos mil líneas y dejaría de poder leerse. Esto + * reexporta todo, así que quien importaba de `$lib/server/db/schema` no se + * entera del cambio. + */ + +export * from './comun'; +export * from './base'; +export * from './catalogo'; +export * from './taller'; +export * from './editorial'; +export * from './facturacion'; +export * from './cuentas'; +export * from './comercio'; +export * from './interaccion'; +export * from './relaciones'; diff --git a/src/lib/server/db/schema/interaccion.ts b/src/lib/server/db/schema/interaccion.ts new file mode 100644 index 0000000..257873e --- /dev/null +++ b/src/lib/server/db/schema/interaccion.ts @@ -0,0 +1,104 @@ +/** + * Lo que hacen quienes han entrado: marcar favoritos y puntuar. + */ + +import { index, integer, pgTable, primaryKey, text } from 'drizzle-orm/pg-core'; +import { ahora, id } from './comun'; +import { usuario } from './cuentas'; + +/* ------------------------------------------------------------------------- + * Favoritos + * ---------------------------------------------------------------------- */ + +/** + * Los temas que una cuenta ha marcado. + * + * Es una lista personal, no una compra ni un voto: se puede marcar cualquier + * tema, este a la venta o no, y no dice nada publico sobre el. Por eso no lleva + * ningun contador agregado. + * + * La clave primaria es el par cuenta-tema, asi que marcar dos veces lo mismo no + * duplica nada. El indice por cuenta es para pintar la lista, que es la + * consulta que se hace de verdad. + */ +export const favorito = pgTable( + 'favorito', + { + usuarioId: text('usuario_id') + .notNull() + .references(() => usuario.id, { onDelete: 'cascade' }), + cancionSlug: text('cancion_slug').notNull(), + marcadoEn: ahora('marcado_en') + }, + (t) => [ + primaryKey({ columns: [t.usuarioId, t.cancionSlug] }), + index('idx_favorito_usuario').on(t.usuarioId) + ] +); + +/* ------------------------------------------------------------------------- + * Valoraciones + * ---------------------------------------------------------------------- */ + +/** + * Lo que una cuenta le pone a un tema, de 1 a 5. + * + * La clave primaria es el par cuenta-tema, asi que nadie puede votar dos veces + * lo mismo: volver a votar reemplaza el voto anterior. Eso lo garantiza la base + * de datos, no el codigo de la accion, que es donde tiene que estar. + * + * El indice por `cancion_slug` es para la media: se calcula por tema, y sin el + * habria que recorrer la tabla entera en cada ficha. + */ +export const valoracion = pgTable( + 'valoracion', + { + usuarioId: text('usuario_id') + .notNull() + .references(() => usuario.id, { onDelete: 'cascade' }), + cancionSlug: text('cancion_slug').notNull(), + /** De 1 a 5. El rango lo comprueba tambien la aplicacion al guardar. */ + puntuacion: integer('puntuacion').notNull(), + creadaEn: ahora('creada_en'), + actualizadaEn: ahora('actualizada_en') + }, + (t) => [ + primaryKey({ columns: [t.usuarioId, t.cancionSlug] }), + index('idx_valoracion_cancion').on(t.cancionSlug) + ] +); + +/** + * Cada escucha. + * + * Se conservan por persona: decisión tomada, y con consecuencias fuera de esta + * tabla —la política de privacidad afirmaba que no se hacía seguimiento y hay + * que reescribirla antes de que esto se publique—. + * + * `segundos` no es un adorno: una reproducción de tres segundos no es una + * escucha, y contarla igual falsea la cifra que se quiere mirar. + * + * Esta tabla crece sin parar, así que su mantenimiento es parte del diseño y no + * un apaño para más adelante: se agrega por día y el detalle se borra a los + * noventa. `dia` está aparte del instante justamente para poder agregar sin + * recorrer millones de filas. + */ +export const reproduccion = pgTable( + 'reproduccion', + { + id: id(), + /** La clave foránea a `cancion` entra en la fase 3, con las demás. */ + cancionSlug: text('cancion_slug').notNull(), + /** Nulo si no había entrado. */ + usuarioId: text('usuario_id').references(() => usuario.id, { onDelete: 'set null' }), + segundos: integer('segundos').notNull().default(0), + /** ISO `YYYY-MM-DD`, para agregar. */ + dia: text('dia').notNull(), + creadoEn: ahora('creado_en') + }, + (t) => [ + index('reproduccion_dia').on(t.dia), + index('reproduccion_cancion').on(t.cancionSlug), + index('reproduccion_usuario').on(t.usuarioId) + ] +); diff --git a/src/lib/server/db/schema/relaciones.ts b/src/lib/server/db/schema/relaciones.ts new file mode 100644 index 0000000..12fb5f4 --- /dev/null +++ b/src/lib/server/db/schema/relaciones.ts @@ -0,0 +1,44 @@ +/** + * Relaciones para el cliente de consultas de Drizzle. + * + * Van aparte de las tablas: describen cómo se navegan, no cómo se guardan, y + * mezclarlas obliga a que cada archivo conozca a los demás. + */ + +import { relations } from 'drizzle-orm'; +import { usuario } from './cuentas'; +import { carrito, carritoItem, compra, pedido, pedidoItem } from './comercio'; +import { favorito, valoracion } from './interaccion'; +import { sesion } from './cuentas'; + +export const relacionesUsuario = relations(usuario, ({ many }) => ({ + sesiones: many(sesion), + pedidos: many(pedido), + compras: many(compra), + valoraciones: many(valoracion), + favoritos: many(favorito) +})); + +export const relacionesCarrito = relations(carrito, ({ many, one }) => ({ + items: many(carritoItem), + usuario: one(usuario, { fields: [carrito.usuarioId], references: [usuario.id] }) +})); + +export const relacionesCarritoItem = relations(carritoItem, ({ one }) => ({ + carrito: one(carrito, { fields: [carritoItem.carritoId], references: [carrito.id] }) +})); + +export const relacionesPedido = relations(pedido, ({ many, one }) => ({ + items: many(pedidoItem), + usuario: one(usuario, { fields: [pedido.usuarioId], references: [usuario.id] }) +})); + +export const relacionesPedidoItem = relations(pedidoItem, ({ one }) => ({ + pedido: one(pedido, { fields: [pedidoItem.pedidoId], references: [pedido.id] }) +})); + +export type Usuario = typeof usuario.$inferSelect; +export type Pedido = typeof pedido.$inferSelect; +export type Compra = typeof compra.$inferSelect; +export type Valoracion = typeof valoracion.$inferSelect; +export type Favorito = typeof favorito.$inferSelect; diff --git a/src/lib/server/db/schema/taller.ts b/src/lib/server/db/schema/taller.ts new file mode 100644 index 0000000..adcfb56 --- /dev/null +++ b/src/lib/server/db/schema/taller.ts @@ -0,0 +1,624 @@ +/** + * El taller: géneros hispanoamericanos, sus fichas, el glosario y los prompts. + * + * Es el área que más creció al modelarla. La ficha de género era una tarjeta + * con cinco campos y ha acabado siendo la pieza más rica del sitio, porque el + * catálogo de géneros aspira a ser completo y se llena poco a poco. + * + * La distinción que sostiene todo esto: un `genero` NO es un `estilo`. El + * estilo es cómo el titular ordena su obra; el género es música + * hispanoamericana descrita para poder escribirla. Compartían palabra y por eso + * compartían nombre en el código. + */ + +import { + boolean, + char, + index, + integer, + jsonb, + pgTable, + primaryKey, + text, + uniqueIndex +} from 'drizzle-orm/pg-core'; +import { ahora, estado, id, instante, procedencia } from './comun'; +import { medio, pais, persona } from './base'; +import { cancion, etiqueta } from './catalogo'; + +/* ------------------------------------------------------------------------- + * Géneros + * ---------------------------------------------------------------------- */ + +/** + * Bolero, tango, cumbia, vallenato. + * + * En columnas va solo lo suyo y lo que no cabe en una tabla auxiliar. Lo + * comparable —metro, compás, temática— sale a vocabularios propios: con ocho + * géneros se lee en prosa, con cien hay que poder preguntar. + */ +export const genero = pgTable( + 'genero', + { + id: id(), + slug: text('slug').notNull().unique(), + nombre: text('nombre').notNull(), + resumen: text('resumen').notNull().default(''), + + origenLugar: text('origen_lugar'), + /** Ordenable, que en el texto libre de antes no lo era. */ + origenAno: integer('origen_ano'), + + tempo: text('tempo'), + rima: text('rima'), + + /** + * El matiz que no cabe en la tabla auxiliar y es lo que hace útil la + * ficha: «a menudo alternados», «también octosílabo en la milonga». + */ + metricaNota: text('metrica_nota'), + compasNota: text('compas_nota'), + + /** + * Rasgos de escritura, en array y no en tabla: son frases de verdad + * —«Tratamiento de usted o de tú, nunca coloquial»— y convertirlas en + * vocabulario sería inventar etiquetas que nadie va a reutilizar. + */ + rasgos: text('rasgos').array().$type(), + + estado: estado(), + ...procedencia(), + creadoEn: ahora('creado_en'), + actualizadoEn: instante('actualizado_en') + }, + (t) => [index('genero_nombre').on(t.nombre), index('genero_origen_ano').on(t.origenAno)] +); + +/** + * De qué deriva un género, y qué deriva de él. + * + * **Sustituye a un `padre_id`, que no aguantaba.** Un género no deriva de uno: + * el bolero-son es bolero *y* son; la salsa viene del son, el mambo y la + * guaracha; la bachata, del bolero, el son y el merengue. Con un solo padre, la + * primera fusión obliga a elegir cuál de los dos mentir. + * + * `influye` es el tercer tipo, más débil que derivar, y ya estaba escrito en el + * contenido dentro del campo equivocado: `son-cubano.md` mete «herencia de la + * décima y la copla españolas» dentro de `metrica` porque no había dónde + * ponerlo. + * + * El grafo guarda la verdad; la arista marcada como `principal` —única por + * género— da el árbol de navegación. Así no hay que elegir entre las dos cosas. + */ +export const generoOrigen = pgTable( + 'genero_origen', + { + generoId: text('genero_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }), + origenId: text('origen_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }), + tipo: text('tipo', { enum: ['deriva', 'fusiona', 'influye'] }).notNull(), + principal: boolean('principal').notNull().default(false), + nota: text('nota'), + orden: integer('orden').notNull().default(0) + }, + (t) => [ + primaryKey({ columns: [t.generoId, t.origenId, t.tipo] }), + index('genero_origen_origen').on(t.origenId) + ] +); + +/** + * En qué países vive un género. + * + * `cuna` o `adoptado`, porque un género no es de un país: nace en uno y se + * canta en varios. La cumbia nace colombiana y se hace mexicana, peruana y + * argentina; el tango es de las dos orillas del Plata. Un solo país sería + * mentira. + */ +export const generoPais = pgTable( + 'genero_pais', + { + generoId: text('genero_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }), + paisCodigo: char('pais_codigo', { length: 2 }) + .notNull() + .references(() => pais.codigo), + relacion: text('relacion', { enum: ['cuna', 'adoptado'] }) + .notNull() + .default('adoptado'), + nota: text('nota') + }, + (t) => [primaryKey({ columns: [t.generoId, t.paisCodigo] })] +); + +/** + * Toda la prosa de la ficha, en secciones ordenadas. + * + * Introducción, historia, estructura de las canciones, motivación y temática. Y + * lo que surja mañana: con una columna por bloque, en un mes hay quince + * columnas y la mitad vacías; siendo secciones, un apartado nuevo no cuesta una + * migración. + */ +export const generoSeccion = pgTable( + 'genero_seccion', + { + id: id(), + generoId: text('genero_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }), + /** introduccion · historia · estructura · motivacion · … */ + clave: text('clave').notNull(), + titulo: text('titulo').notNull(), + cuerpo: text('cuerpo').notNull(), + orden: integer('orden').notNull().default(0) + }, + (t) => [uniqueIndex('genero_seccion_clave').on(t.generoId, t.clave)] +); + +/** + * Cómo se llama ese género donde se toca. + * + * *Son cubano*, *son jarocho*, *son huasteco*, *son montuno*: cuatro cosas que + * no se parecen y comparten palabra. Con cien fichas, buscar «son» tiene que + * llevar a la que corresponde. + */ +export const generoAlias = pgTable( + 'genero_alias', + { + id: id(), + generoId: text('genero_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }), + nombre: text('nombre').notNull(), + paisCodigo: char('pais_codigo', { length: 2 }).references(() => pais.codigo), + nota: text('nota') + }, + (t) => [index('genero_alias_nombre').on(t.nombre)] +); + +/** De qué habla un género. Comparte vocabulario con las etiquetas del catálogo. */ +export const generoTema = pgTable( + 'genero_tema', + { + generoId: text('genero_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }), + etiquetaId: text('etiqueta_id') + .notNull() + .references(() => etiqueta.id, { onDelete: 'cascade' }), + orden: integer('orden').notNull().default(0) + }, + (t) => [primaryKey({ columns: [t.generoId, t.etiquetaId] })] +); + +/** Un tema propio escrito en ese género, aunque el catálogo lo ordene aparte. */ +export const cancionGenero = pgTable( + 'cancion_genero', + { + cancionId: text('cancion_id') + .notNull() + .references(() => cancion.id, { onDelete: 'cascade' }), + generoId: text('genero_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }) + }, + (t) => [primaryKey({ columns: [t.cancionId, t.generoId] })] +); + +/* ------------------------------------------------------------------------- + * Metro y compás: lo comparable, fuera de la prosa + * ---------------------------------------------------------------------- */ + +/** + * Medidas del verso. + * + * El octosílabo aparece en el bolero, el son y el tango; el endecasílabo en el + * bolero y el tango. Escritos en prosa no se pueden preguntar, y «enséñame los + * géneros en octosílabo» es exactamente lo que un taller de métrica necesita. + * + * `silabas` nulo en la fila `libre`, para que «sin métrica fija» —la bachata: + * «Libre, cercana al habla»— también sea una consulta y no una ausencia. + */ +export const metro = pgTable('metro', { + id: id(), + codigo: text('codigo').notNull().unique(), + nombre: text('nombre').notNull(), + silabas: integer('silabas'), + /** Al glosario: «octosílabo» es también una entrada suya. */ + terminoId: text('termino_id'), + nota: text('nota') +}); + +export const generoMetro = pgTable( + 'genero_metro', + { + generoId: text('genero_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }), + metroId: text('metro_id') + .notNull() + .references(() => metro.id), + predominante: boolean('predominante').notNull().default(false) + }, + (t) => [primaryKey({ columns: [t.generoId, t.metroId] })] +); + +/** + * Compases. + * + * Numerador y denominador aparte del código para poder pedir los binarios o los + * ternarios sin analizar una cadena. + */ +export const compas = pgTable('compas', { + id: id(), + codigo: text('codigo').notNull().unique(), + nombre: text('nombre'), + numerador: integer('numerador').notNull(), + denominador: integer('denominador').notNull(), + nota: text('nota') +}); + +export const generoCompas = pgTable( + 'genero_compas', + { + generoId: text('genero_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }), + compasId: text('compas_id') + .notNull() + .references(() => compas.id), + /** El bolero lleva dos: 2/4 en el clásico, 4/4 en el moderno. */ + principal: boolean('principal').notNull().default(false), + nota: text('nota') + }, + (t) => [primaryKey({ columns: [t.generoId, t.compasId] })] +); + +/* ------------------------------------------------------------------------- + * Instrumentos + * ---------------------------------------------------------------------- */ + +export const FAMILIAS = ['cuerda', 'viento', 'percusion', 'teclado', 'electrofono', 'voz'] as const; + +export const instrumento = pgTable( + 'instrumento', + { + id: id(), + slug: text('slug').notNull().unique(), + nombre: text('nombre').notNull(), + familia: text('familia', { enum: FAMILIAS }).notNull(), + /** «cuerda pulsada», «membranófono». Libre: la clasificación fina varía. */ + subfamilia: text('subfamilia'), + paisCodigo: char('pais_codigo', { length: 2 }).references(() => pais.codigo), + descripcion: text('descripcion'), + /** «la · re · sol · do · mi · la». Dato de oficio, no adorno. */ + afinacion: text('afinacion'), + imagenId: text('imagen_id').references(() => medio.id, { onDelete: 'set null' }), + /** El sonido de muestra es un archivo como cualquier otro. */ + muestraId: text('muestra_id').references(() => medio.id, { onDelete: 'set null' }), + estado: estado(), + ...procedencia() + }, + (t) => [index('instrumento_familia').on(t.familia)] +); + +/** + * Qué instrumentos lleva un género y con qué peso. + * + * `importancia` no es un adorno: sin ella, el tres cubano y una trompeta + * ocasional pesarían igual en la ficha, y no es verdad. + */ +export const generoInstrumento = pgTable( + 'genero_instrumento', + { + generoId: text('genero_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }), + instrumentoId: text('instrumento_id') + .notNull() + .references(() => instrumento.id), + importancia: text('importancia', { enum: ['esencial', 'habitual', 'ocasional'] }) + .notNull() + .default('habitual'), + /** «marca la clave», «lleva el bajo», «hace las introducciones». */ + papel: text('papel'), + orden: integer('orden').notNull().default(0) + }, + (t) => [primaryKey({ columns: [t.generoId, t.instrumentoId] })] +); + +/* ------------------------------------------------------------------------- + * Personas y obras de referencia + * ---------------------------------------------------------------------- */ + +/** Figuras de un género: letristas, compositores, intérpretes, fundadores. */ +export const generoPersona = pgTable( + 'genero_persona', + { + generoId: text('genero_id') + .notNull() + .references(() => genero.id, { onDelete: 'cascade' }), + personaId: text('persona_id') + .notNull() + .references(() => persona.id), + papel: text('papel', { + enum: ['letrista', 'compositor', 'interprete', 'fundador'] + }).notNull(), + nota: text('nota'), + orden: integer('orden').notNull().default(0) + }, + (t) => [primaryKey({ columns: [t.generoId, t.personaId, t.papel] })] +); + +/** + * Obras ajenas que definen un género. + * + * «Letras de referencia de un letrista» y «canciones del género» son la misma + * entidad, mirada desde un lado o desde el otro. + * + * **Aquí no se alojan letras completas.** «Bésame mucho» tiene dueño y lo + * tendrá durante décadas. Se cita título, autor y año, se enlaza a dónde + * escucharla y se reproduce un fragmento breve al amparo del derecho de cita + * —el mismo que invoca nuestro propio aviso legal—. Por eso el campo se llama + * `cita` y es corto, y no `letra`. + */ +export const obraReferencia = pgTable( + 'obra_referencia', + { + id: id(), + slug: text('slug').notNull().unique(), + titulo: text('titulo').notNull(), + ano: integer('ano'), + generoId: text('genero_id').references(() => genero.id, { onDelete: 'set null' }), + /** Por qué importa esta obra. */ + nota: text('nota'), + /** Dónde escucharla. */ + enlace: text('enlace'), + /** Fragmento breve, no la letra. */ + cita: text('cita'), + estado: estado(), + ...procedencia() + }, + (t) => [index('obra_referencia_genero').on(t.generoId)] +); + +export const obraReferenciaPersona = pgTable( + 'obra_referencia_persona', + { + obraId: text('obra_id') + .notNull() + .references(() => obraReferencia.id, { onDelete: 'cascade' }), + personaId: text('persona_id') + .notNull() + .references(() => persona.id), + papel: text('papel', { enum: ['letra', 'musica', 'interprete'] }).notNull(), + orden: integer('orden').notNull().default(0) + }, + (t) => [primaryKey({ columns: [t.obraId, t.personaId, t.papel] })] +); + +/* ------------------------------------------------------------------------- + * Capítulos y glosario + * ---------------------------------------------------------------------- */ + +/** El curso —los principios— y la ficha de cada género. */ +export const capitulo = pgTable( + 'capitulo', + { + id: id(), + slug: text('slug').notNull().unique(), + titulo: text('titulo').notNull(), + tipo: text('tipo', { enum: ['principio', 'ficha'] }).notNull(), + /** Solo en las fichas. */ + generoId: text('genero_id').references(() => genero.id, { onDelete: 'set null' }), + orden: integer('orden').notNull().default(0), + resumen: text('resumen').notNull().default(''), + cuerpo: text('cuerpo').notNull().default(''), + estado: estado(), + creadoEn: ahora('creado_en'), + actualizadoEn: instante('actualizado_en') + }, + (t) => [index('capitulo_tipo').on(t.tipo, t.orden)] +); + +/** La palabra del glosario. Sus sentidos van aparte. */ +export const termino = pgTable('termino', { + id: id(), + slug: text('slug').notNull().unique(), + termino: text('termino').notNull().unique() +}); + +/** + * Un sentido del término, numerado. + * + * «Clave» es instrumento de percusión en la primera acepción, patrón rítmico en + * la segunda y armadura de tonalidad en la tercera. Con una sola definición por + * palabra, dos de las tres serían falsas. Por eso la categoría vive aquí y no + * en el término. + */ +export const acepcion = pgTable( + 'acepcion', + { + id: id(), + terminoId: text('termino_id') + .notNull() + .references(() => termino.id, { onDelete: 'cascade' }), + numero: integer('numero').notNull(), + /** metrica · rima · musica · estructura · interpretacion · instrumento */ + categoria: text('categoria').notNull(), + /** Una o dos frases: lo que cabe en un globo sin tapar el texto. */ + definicion: text('definicion').notNull(), + /** Lo que se lee en la página del glosario. */ + ampliacion: text('ampliacion'), + /** Un verso donde se vea. Un término de métrica sin ejemplo no se entiende. */ + ejemplo: text('ejemplo') + }, + (t) => [uniqueIndex('acepcion_numero').on(t.terminoId, t.numero)] +); + +/** + * Las formas en que aparece escrito un término. + * + * «sinalefas», «heptasílabos», «rimas asonantes». Al nivel del término y no de + * la acepción: el plural no cambia de sentido. Sin esto, el marcado solo + * acierta cuando la palabra sale en singular, que es casi nunca. + */ +export const terminoVariante = pgTable( + 'termino_variante', + { + terminoId: text('termino_id') + .notNull() + .references(() => termino.id, { onDelete: 'cascade' }), + forma: text('forma').notNull() + }, + (t) => [primaryKey({ columns: [t.terminoId, t.forma] })] +); + +/** + * Dónde se marca cada acepción. + * + * Apunta a una **acepción**, no a un término: en un texto sobre el bolero, + * «clave» es el patrón rítmico y no la armadura. Eso resuelve la duda del + * marcado automático: sabe encontrar la palabra pero no elegir el sentido, así + * que propone y una persona confirma. + * + * Se calcula al guardar el capítulo, no al pintarlo: buscar veinte términos en + * cada texto en cada petición es trabajo repetido. Y siendo tabla se puede + * desmarcar una aparición suelta cuando la palabra esté usada en otro sentido. + */ +export const capituloAcepcion = pgTable( + 'capitulo_acepcion', + { + capituloId: text('capitulo_id') + .notNull() + .references(() => capitulo.id, { onDelete: 'cascade' }), + acepcionId: text('acepcion_id') + .notNull() + .references(() => acepcion.id, { onDelete: 'cascade' }), + /** Propuesto por el marcado automático y aún sin confirmar. */ + automatico: boolean('automatico').notNull().default(true), + confirmado: boolean('confirmado').notNull().default(false) + }, + (t) => [primaryKey({ columns: [t.capituloId, t.acepcionId] })] +); + +/* ------------------------------------------------------------------------- + * Prompts para IA generativa + * ---------------------------------------------------------------------- */ + +/** + * Un modelo concreto, no una marca. + * + * **El prompt depende del modelo, no de la IA.** Uno de Suno v3 no es uno de + * v4: cambian la sintaxis, las etiquetas y lo que el modelo ignora. Si esto + * apuntara a «Suno» a secas, el día que salga la versión siguiente quedaría una + * biblioteca de prompts sin saber para qué servía cada uno. + */ +export const modeloIa = pgTable( + 'modelo_ia', + { + id: id(), + codigo: text('codigo').notNull().unique(), + familia: text('familia').notNull(), + version: text('version').notNull(), + nombre: text('nombre').notNull(), + vigente: boolean('vigente').notNull().default(true), + notaSintaxis: text('nota_sintaxis'), + /** + * JSON Schema con la forma que toman los prompts de este modelo. Se validan + * contra él al guardar, y el panel puede pintar el formulario a partir de + * él sin una pantalla por cada IA. + */ + esquema: jsonb('esquema') + }, + (t) => [index('modelo_ia_familia').on(t.familia)] +); + +/** + * Ánimo, codificado. + * + * La escala es la **GEMS** (*Geneva Emotional Music Scale*), hecha para emoción + * musical y no para emoción en general: asombro, trascendencia, ternura, + * nostalgia, serenidad, fuerza, alegría vital, tensión y tristeza. Más los dos + * ejes del modelo circumplejo de Russell —valencia y energía—, que sirven para + * ordenar e interpolar y que además son redactables: «alta energía, valencia + * baja» es una instrucción; «tensión» a secas, no. + * + * Se llama `animo` y no `version` a propósito: `version` ya significa otra cosa + * en este modelo y con las dos compartiendo nombre ninguna consulta se leería + * sola. + */ +export const animo = pgTable('animo', { + id: id(), + codigo: text('codigo').notNull().unique(), + nombre: text('nombre').notNull(), + /** El factor GEMS del que sale, si sale de uno. */ + gems: text('gems'), + /** −1 a 1. */ + valencia: integer('valencia'), + /** 0 a 100. */ + energia: integer('energia'), + descripcion: text('descripcion') +}); + +/** + * Un prompt de referencia para un género y un ánimo. + * + * El contenido va abierto **y aquí sí toca**: la forma la define un tercero y + * cambia sin avisarnos. Es el caso contrario al del contenido del sitio, donde + * el esquema es nuestro y por eso ahí sí se modela. + * + * Abierto no es sin validar: se comprueba contra el `esquema` de su modelo. Y + * lo que se consulta —género, ánimo, modelo, valoración, idioma— se queda en + * columnas, para que «los prompts que funcionaron para el bolero» siga siendo + * una consulta trivial. + * + * La versión del modelo NO se repite dentro del JSON: vive en + * `modelo_ia.version`. Si una API la exige en el payload, se compone al enviar. + */ +export const prompt = pgTable( + 'prompt', + { + id: id(), + modeloIaId: text('modelo_ia_id') + .notNull() + .references(() => modeloIa.id), + generoId: text('genero_id').references(() => genero.id, { onDelete: 'cascade' }), + animoId: text('animo_id').references(() => animo.id), + /** En inglés suelen funcionar mejor, incluso para música en español. */ + idioma: text('idioma').notNull().default('en'), + + contenido: jsonb('contenido').notNull(), + + notas: text('notas'), + /** Si funcionó. Sin esto se acumulan prompts sin saber cuáles servían. */ + valoracion: integer('valoracion'), + /** El resultado, para poder oírlo. */ + ejemploId: text('ejemplo_id').references(() => medio.id, { onDelete: 'set null' }), + /** Varios por combinación; uno marcado. Esto se itera, no se acierta. */ + preferido: boolean('preferido').notNull().default(false), + + /** + * Nacen privados. No porque publicarlos esté mal, sino porque es una + * decisión de posicionamiento sin tomar y esta es la reversible: + * publicarlos mañana es cambiar una columna; despublicarlos cuando ya + * están indexados, no. + */ + visibilidad: text('visibilidad', { enum: ['privado', 'taller', 'publico'] }) + .notNull() + .default('privado'), + + estado: estado(), + creadoEn: ahora('creado_en'), + actualizadoEn: instante('actualizado_en') + }, + (t) => [ + index('prompt_genero').on(t.generoId), + index('prompt_modelo').on(t.modeloIaId), + index('prompt_animo').on(t.animoId) + ] +);