Compare commits
24 Commits
0f376a8b75
...
v2.1.0-sta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31172cc91d | ||
|
|
5c17a9846a | ||
|
|
b9a3118c84 | ||
|
|
cfd2eb0624 | ||
|
|
edf82bf7fd | ||
|
|
60c9c81224 | ||
|
|
1bab6874a9 | ||
|
|
933d00ad58 | ||
|
|
059dfa071e | ||
|
|
2d13982d92 | ||
|
|
92a7923a26 | ||
|
|
af0ec65be5 | ||
|
|
157e108322 | ||
|
|
bf963e8894 | ||
|
|
2f39ce555c | ||
|
|
52295a78d0 | ||
|
|
307a54dabc | ||
|
|
a17e810ea8 | ||
|
|
b813622e30 | ||
|
|
b1ca0e0795 | ||
|
|
d91ea6b2ff | ||
|
|
10c665d25c | ||
|
|
df2c33f2d9 | ||
|
|
ad35c46a7a |
10
.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.git
|
||||
.next
|
||||
node_modules
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.md
|
||||
sprints/
|
||||
supabase/
|
||||
.claude/
|
||||
@@ -6,3 +6,9 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key-publica>
|
||||
|
||||
# NUNCA agregar SUPABASE_SERVICE_ROLE_KEY como variable NEXT_PUBLIC_ ni en el cliente
|
||||
# La service_role bypasa RLS — solo para Edge Functions / backend server-side
|
||||
# Usada por app/admin/responses (Server Component) para listar respuestas sin RLS
|
||||
SUPABASE_SERVICE_ROLE_KEY=
|
||||
|
||||
# IP hash — variable server-only (NUNCA NEXT_PUBLIC_)
|
||||
# Usada para deduplicación anónima. Generá un string aleatorio largo.
|
||||
IP_HASH_SALT=
|
||||
|
||||
3
.gitignore
vendored
@@ -14,6 +14,9 @@ supabase/.temp/
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# Next.js build output
|
||||
.next/
|
||||
|
||||
# Supabase — claves y credenciales
|
||||
*.key
|
||||
service_role*
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"pages": {}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"polyfillFiles": [
|
||||
"static/chunks/polyfills.js"
|
||||
],
|
||||
"devFiles": [],
|
||||
"ampDevFiles": [],
|
||||
"lowPriorityFiles": [
|
||||
"static/development/_buildManifest.js",
|
||||
"static/development/_ssgManifest.js"
|
||||
],
|
||||
"rootMainFiles": [],
|
||||
"rootMainFilesTree": {},
|
||||
"pages": {
|
||||
"/_app": []
|
||||
},
|
||||
"ampFirstPages": []
|
||||
}
|
||||
1
.next/cache/.rscinfo
vendored
@@ -1 +0,0 @@
|
||||
{"encryption.key":"rCrwV8YThn7yWBHzp4+yH4ABfW8goSONtUiZDNhogWc=","encryption.expire_at":1781610552095}
|
||||
1
.next/cache/.tsbuildinfo
vendored
BIN
.next/cache/webpack/client-development/0.pack.gz
vendored
BIN
.next/cache/webpack/client-development/1.pack.gz
vendored
BIN
.next/cache/webpack/client-development/2.pack.gz
vendored
BIN
.next/cache/webpack/client-development/3.pack.gz
vendored
BIN
.next/cache/webpack/client-development/4.pack.gz
vendored
BIN
.next/cache/webpack/client-development/5.pack.gz
vendored
BIN
.next/cache/webpack/client-development/index.pack.gz
vendored
BIN
.next/cache/webpack/client-production/0.pack
vendored
BIN
.next/cache/webpack/client-production/1.pack
vendored
BIN
.next/cache/webpack/client-production/2.pack
vendored
BIN
.next/cache/webpack/client-production/3.pack
vendored
BIN
.next/cache/webpack/client-production/4.pack
vendored
BIN
.next/cache/webpack/client-production/index.pack
vendored
BIN
.next/cache/webpack/client-production/index.pack.old
vendored
BIN
.next/cache/webpack/edge-server-production/0.pack
vendored
BIN
.next/cache/webpack/server-development/0.pack.gz
vendored
BIN
.next/cache/webpack/server-development/1.pack.gz
vendored
BIN
.next/cache/webpack/server-development/2.pack.gz
vendored
BIN
.next/cache/webpack/server-development/3.pack.gz
vendored
BIN
.next/cache/webpack/server-development/index.pack.gz
vendored
BIN
.next/cache/webpack/server-production/0.pack
vendored
BIN
.next/cache/webpack/server-production/1.pack
vendored
BIN
.next/cache/webpack/server-production/2.pack
vendored
BIN
.next/cache/webpack/server-production/3.pack
vendored
BIN
.next/cache/webpack/server-production/index.pack
vendored
BIN
.next/cache/webpack/server-production/index.pack.old
vendored
@@ -1 +0,0 @@
|
||||
{"type": "commonjs"}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
self.__INTERCEPTION_ROUTE_REWRITE_MANIFEST="[]"
|
||||
@@ -1,19 +0,0 @@
|
||||
globalThis.__BUILD_MANIFEST = {
|
||||
"polyfillFiles": [
|
||||
"static/chunks/polyfills.js"
|
||||
],
|
||||
"devFiles": [],
|
||||
"ampDevFiles": [],
|
||||
"lowPriorityFiles": [],
|
||||
"rootMainFiles": [],
|
||||
"rootMainFilesTree": {},
|
||||
"pages": {
|
||||
"/_app": []
|
||||
},
|
||||
"ampFirstPages": []
|
||||
};
|
||||
globalThis.__BUILD_MANIFEST.lowPriorityFiles = [
|
||||
"/static/" + process.env.__NEXT_BUILD_ID + "/_buildManifest.js",
|
||||
,"/static/" + process.env.__NEXT_BUILD_ID + "/_ssgManifest.js",
|
||||
|
||||
];
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"middleware": {},
|
||||
"functions": {},
|
||||
"sortedMiddleware": []
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
self.__REACT_LOADABLE_MANIFEST="{}"
|
||||
@@ -1 +0,0 @@
|
||||
self.__NEXT_FONT_MANIFEST="{\"pages\":{},\"app\":{},\"appUsingSizeAdjust\":false,\"pagesUsingSizeAdjust\":false}"
|
||||
@@ -1 +0,0 @@
|
||||
{"pages":{},"app":{},"appUsingSizeAdjust":false,"pagesUsingSizeAdjust":false}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
self.__RSC_SERVER_MANIFEST="{\n \"node\": {},\n \"edge\": {},\n \"encryptionKey\": \"process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY\"\n}"
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"node": {},
|
||||
"edge": {},
|
||||
"encryptionKey": "rCrwV8YThn7yWBHzp4+yH4ABfW8goSONtUiZDNhogWc="
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
self.__BUILD_MANIFEST = (function(a){return {__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:a,__routerFilterDynamic:a,sortedPages:["\u002F_app"]}}(void 0));self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()
|
||||
@@ -1 +0,0 @@
|
||||
self.__SSG_MANIFEST=new Set;self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
|
||||
141
.next/types/cache-life.d.ts
vendored
@@ -1,141 +0,0 @@
|
||||
// Type definitions for Next.js cacheLife configs
|
||||
|
||||
declare module 'next/cache' {
|
||||
export { unstable_cache } from 'next/dist/server/web/spec-extension/unstable-cache'
|
||||
export {
|
||||
revalidateTag,
|
||||
revalidatePath,
|
||||
unstable_expireTag,
|
||||
unstable_expirePath,
|
||||
} from 'next/dist/server/web/spec-extension/revalidate'
|
||||
export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store'
|
||||
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"default"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 900 seconds (15 minutes)
|
||||
* expire: never
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 15 minutes, start revalidating new values in the background.
|
||||
* It lives for the maximum age of the server cache. If this entry has no traffic for a while, it may serve an old value the next request.
|
||||
*/
|
||||
export function unstable_cacheLife(profile: "default"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"seconds"` profile.
|
||||
* ```
|
||||
* stale: 0 seconds
|
||||
* revalidate: 1 seconds
|
||||
* expire: 60 seconds (1 minute)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 0 seconds before checking with the server.
|
||||
* If the server receives a new request after 1 seconds, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 1 minute it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function unstable_cacheLife(profile: "seconds"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"minutes"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 60 seconds (1 minute)
|
||||
* expire: 3600 seconds (1 hour)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 1 minute, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 1 hour it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function unstable_cacheLife(profile: "minutes"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"hours"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 3600 seconds (1 hour)
|
||||
* expire: 86400 seconds (1 day)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 1 hour, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 1 day it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function unstable_cacheLife(profile: "hours"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"days"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 86400 seconds (1 day)
|
||||
* expire: 604800 seconds (1 week)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 1 day, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 1 week it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function unstable_cacheLife(profile: "days"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"weeks"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 604800 seconds (1 week)
|
||||
* expire: 2592000 seconds (30 days)
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 1 week, start revalidating new values in the background.
|
||||
* If this entry has no traffic for 30 days it will expire. The next request will recompute it.
|
||||
*/
|
||||
export function unstable_cacheLife(profile: "weeks"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` for a timespan defined by the `"max"` profile.
|
||||
* ```
|
||||
* stale: 300 seconds (5 minutes)
|
||||
* revalidate: 2592000 seconds (30 days)
|
||||
* expire: never
|
||||
* ```
|
||||
*
|
||||
* This cache may be stale on clients for 5 minutes before checking with the server.
|
||||
* If the server receives a new request after 30 days, start revalidating new values in the background.
|
||||
* It lives for the maximum age of the server cache. If this entry has no traffic for a while, it may serve an old value the next request.
|
||||
*/
|
||||
export function unstable_cacheLife(profile: "max"): void
|
||||
|
||||
/**
|
||||
* Cache this `"use cache"` using a custom timespan.
|
||||
* ```
|
||||
* stale: ... // seconds
|
||||
* revalidate: ... // seconds
|
||||
* expire: ... // seconds
|
||||
* ```
|
||||
*
|
||||
* This is similar to Cache-Control: max-age=`stale`,s-max-age=`revalidate`,stale-while-revalidate=`expire-revalidate`
|
||||
*
|
||||
* If a value is left out, the lowest of other cacheLife() calls or the default, is used instead.
|
||||
*/
|
||||
export function unstable_cacheLife(profile: {
|
||||
/**
|
||||
* This cache may be stale on clients for ... seconds before checking with the server.
|
||||
*/
|
||||
stale?: number,
|
||||
/**
|
||||
* If the server receives a new request after ... seconds, start revalidating new values in the background.
|
||||
*/
|
||||
revalidate?: number,
|
||||
/**
|
||||
* If this entry has no traffic for ... seconds it will expire. The next request will recompute it.
|
||||
*/
|
||||
expire?: number
|
||||
}): void
|
||||
|
||||
|
||||
export { cacheTag as unstable_cacheTag } from 'next/dist/server/use-cache/cache-tag'
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"type": "module"}
|
||||
67
BACKLOG.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# BACKLOG.md
|
||||
|
||||
> Ítems pendientes que no entran en el sprint actual. El agente NO los ejecuta
|
||||
> sin aprobación del director (Nivel 3 salvo indicación contraria).
|
||||
|
||||
---
|
||||
|
||||
## Seguridad
|
||||
|
||||
### [SEC-001] Actualizar Next.js — CVE pendiente en 15.3.3
|
||||
- **Estado:** Pendiente
|
||||
- **Prioridad:** Alta
|
||||
- **Contexto:** Next.js 15.3.3 tiene un CVE conocido. La versión no se actualizó
|
||||
en la tarea del 2026-06-02 para no mezclar cambios con el fix de producción de
|
||||
chromedriver. Actualizar en el próximo ciclo de mantenimiento.
|
||||
- **Acción:** `npm install next@latest` + verificar que el build y la app sigan
|
||||
funcionando antes de deployar.
|
||||
- **Nota:** revisar changelog de Next.js por breaking changes antes de actualizar.
|
||||
|
||||
---
|
||||
|
||||
## Técnico / Deuda
|
||||
|
||||
### [TECH-001] `npm audit` reporta 2 vulnerabilidades en deps de producción
|
||||
- **Estado:** Pendiente de revisión
|
||||
- **Contexto:** `npm audit` señala 1 moderate + 1 critical. Evaluar con
|
||||
`npm audit --json` si afectan el runtime de la app o solo herramientas de build.
|
||||
- **Acción:** `npm audit fix` (o fix manual) y re-verificar build.
|
||||
|
||||
### [TECH-002] Mockup `formulario_mapeo.html` no encontrado en el árbol de trabajo
|
||||
- **Estado:** Pendiente
|
||||
- **Contexto:** El BRIEF Sprint 2 lo cita como "contrato visual". El agente diseñó
|
||||
la UI basándose en SEED_SPEC.md. Requiere validación visual del director contra
|
||||
el mockup original.
|
||||
|
||||
### [TECH-003] Auditoría a11y interactiva pendiente (foco, aria-expanded, widgets)
|
||||
- **Estado:** Pendiente
|
||||
- **Contexto:** La auditoría de Etapa 2A se corrió sobre el HTML SSR estático
|
||||
(jsdom). La verificación de componentes interactivos (foco al abrir bloque
|
||||
condicional, aria-expanded en triggers, operación solo con teclado) requiere
|
||||
auditoría en browser real.
|
||||
- **Acción:** Correr axe-cli con Chrome 149+ (o actualizar Chrome a 149) y
|
||||
hacer test manual de navegación por teclado.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
*(Sprint 3 y posteriores — ver ARCHITECTURE.md y BRIEF por sprint)*
|
||||
|
||||
### [BACKLOG-001] Consentimiento inicial editable desde admin
|
||||
- **Estado:** hoy el texto del consentimiento está hardcoded en `consent-screen.tsx`.
|
||||
- **Necesidad:** cuando se construya el motor de formularios (builder), el texto del
|
||||
consentimiento debe ser editable por encuesta desde la administración, para reflejar
|
||||
cambios en política de privacidad o ajustes específicos por tipo de relevamiento.
|
||||
- **Bloquea:** implementación del builder visual.
|
||||
- **Decisión cerrada:** la edición es desde admin, no desde el código.
|
||||
|
||||
---
|
||||
|
||||
- Dashboard / reportes (Sprint 3)
|
||||
- Formulario de consentimiento + persistencia de respuestas (Etapa 2B)
|
||||
- Builder visual de encuestas (post-validación del caso fijo)
|
||||
- Integración LLM para análisis de texto libre (diferenciador)
|
||||
- Modo B / controller_role dual (reservado en esquema)
|
||||
- Columnas `qi_gender` / `qi_modality` en `responses` — decisión Nivel 3 abierta
|
||||
(ver hallazgo en SEED_SPEC.md)
|
||||
43
Dockerfile
Normal file
@@ -0,0 +1,43 @@
|
||||
FROM node:20-alpine AS base
|
||||
|
||||
# ── Dependencias ──────────────────────────────────────────────────────────────
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# ── Build ─────────────────────────────────────────────────────────────────────
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Las variables NEXT_PUBLIC_* se hornean en el bundle en build time.
|
||||
# Pasarlas como build args desde Dokploy (Environment → Build Args).
|
||||
ARG NEXT_PUBLIC_SUPABASE_URL
|
||||
ARG NEXT_PUBLIC_SUPABASE_ANON_KEY
|
||||
ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL
|
||||
ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# ── Runner ────────────────────────────────────────────────────────────────────
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs \
|
||||
&& adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
384
INSTALL_SUPABASE.md
Normal file
@@ -0,0 +1,384 @@
|
||||
# INSTALL_SUPABASE.md — Supabase self-hosted en Dokploy v0.29.4
|
||||
|
||||
> Guía operativa basada en la instalación real del proyecto "Datos País sobre el Cuidado" (junio 2026).
|
||||
> Registra los pasos exactos, errores conocidos y sus soluciones.
|
||||
|
||||
---
|
||||
|
||||
## Decisiones de arquitectura
|
||||
|
||||
- **Supabase self-hosted completo** (Postgres + Auth + Storage + Realtime + Studio + Kong).
|
||||
- **Partida limpia** — no se migran datos desde Cloud, se aplican las migraciones del repo.
|
||||
- **Gestión vía Git** — el compose y los volumes viven en un repo dedicado; Dokploy clona ese repo.
|
||||
Las variables de entorno viven en Dokploy (no en el repo).
|
||||
- **Backups locales** diarios, retención 30 días. Remoto: TECH-DEBT-006.
|
||||
- **Studio** no expuesto a internet. Acceso solo vía SSH tunnel.
|
||||
|
||||
---
|
||||
|
||||
## Requisitos previos
|
||||
|
||||
- VPS Ubuntu 22.04+ con Docker 29+ instalado.
|
||||
- Dokploy v0.29.4 instalado y accesible.
|
||||
- Repo Git disponible (Gitea o GitHub) con acceso desde el VPS y desde Dokploy.
|
||||
- Acceso SSH al VPS.
|
||||
- Supabase CLI en la máquina de desarrollo local.
|
||||
|
||||
---
|
||||
|
||||
## PARTE 1 — Preparar el repo de Supabase
|
||||
|
||||
### 1.1 Descargar el stack oficial en el VPS
|
||||
|
||||
```bash
|
||||
curl -fsSL https://supabase.link/setup.sh | sh
|
||||
```
|
||||
|
||||
Crea `~/supabase-project/` con esta estructura:
|
||||
|
||||
```
|
||||
~/supabase-project/
|
||||
├── docker-compose.yml
|
||||
├── volumes/
|
||||
│ ├── api/
|
||||
│ │ ├── kong.yml <- API gateway (EDITAR: ver 1.2)
|
||||
│ │ └── kong-entrypoint.sh
|
||||
│ ├── db/
|
||||
│ │ ├── _supabase.sql <- CRITICO: crea la base _supabase
|
||||
│ │ ├── roles.sql
|
||||
│ │ ├── jwt.sql
|
||||
│ │ ├── realtime.sql
|
||||
│ │ ├── pooler.sql
|
||||
│ │ ├── webhooks.sql
|
||||
│ │ ├── logs.sql
|
||||
│ │ └── init/data.sql
|
||||
│ └── pooler/
|
||||
│ └── pooler.exs
|
||||
```
|
||||
|
||||
> Estos archivos son los scripts de inicializacion que Postgres ejecuta la primera vez.
|
||||
> Sin ellos la base _supabase no se crea y el stack no funciona.
|
||||
> Por eso NO se puede usar "docker raw" en Dokploy — ese modo no tiene contexto del directorio.
|
||||
|
||||
### 1.2 Ajustar kong.yml
|
||||
|
||||
El kong.yml define las credenciales del API gateway. Verificar que use los mismos
|
||||
nombres de variables que se definen en Dokploy:
|
||||
|
||||
```bash
|
||||
grep -n "keyauth" ~/supabase-project/volumes/api/kong.yml
|
||||
```
|
||||
|
||||
Deben quedar dos bloques con UNA sola entrada cada uno:
|
||||
|
||||
```yaml
|
||||
keyauth_credentials:
|
||||
- key: $ANON_KEY # nombre exacto de la variable en Dokploy
|
||||
|
||||
keyauth_credentials:
|
||||
- key: $SERVICE_ROLE_KEY # nombre exacto de la variable en Dokploy
|
||||
```
|
||||
|
||||
ERROR comun: si los dos bloques resuelven al mismo valor JWT, Kong falla con
|
||||
"uniqueness violation". Verificar que ANON_KEY y SERVICE_ROLE_KEY son distintos.
|
||||
|
||||
### 1.3 Crear .gitignore
|
||||
|
||||
```bash
|
||||
cat > ~/supabase-project/.gitignore << 'EOF'
|
||||
.env
|
||||
.env.local
|
||||
volumes/db/data/
|
||||
volumes/storage/
|
||||
volumes/logs/
|
||||
*.log
|
||||
EOF
|
||||
```
|
||||
|
||||
### 1.4 Push al repo Git
|
||||
|
||||
```bash
|
||||
cd ~/supabase-project
|
||||
git init
|
||||
git add docker-compose.yml volumes/api/ volumes/db/ volumes/pooler/ .gitignore
|
||||
git commit -m "chore: supabase self-hosted stack inicial"
|
||||
git remote add origin https://TU_GIT/usuario/supabase-repo.git
|
||||
git push -u origin master
|
||||
```
|
||||
|
||||
El .env con los secrets NO se sube (esta en .gitignore).
|
||||
|
||||
---
|
||||
|
||||
## PARTE 2 — Generar los secrets
|
||||
|
||||
Usar siempre openssl rand -hex (solo hex, sin caracteres especiales que rompan el escaping de Docker).
|
||||
|
||||
### 2.1 Valores base (en el VPS)
|
||||
|
||||
```bash
|
||||
echo "POSTGRES_PASSWORD=$(openssl rand -hex 24)"
|
||||
echo "JWT_SECRET=$(openssl rand -hex 40)"
|
||||
echo "DASHBOARD_PASSWORD=$(openssl rand -hex 16)"
|
||||
echo "SECRET_KEY_BASE=$(openssl rand -hex 40)"
|
||||
echo "VAULT_ENC_KEY=$(openssl rand -hex 20)"
|
||||
echo "PG_META_CRYPTO_KEY=$(openssl rand -hex 24)"
|
||||
```
|
||||
|
||||
Guardar todos en un gestor de contrasenas antes de continuar.
|
||||
|
||||
### 2.2 Generar ANON_KEY y SERVICE_ROLE_KEY
|
||||
|
||||
Con el JWT_SECRET generado, usar Node.js en el VPS:
|
||||
|
||||
```bash
|
||||
node -e "
|
||||
const crypto = require('crypto');
|
||||
const secret = 'PEGAR_AQUI_JWT_SECRET';
|
||||
|
||||
function b64url(obj) {
|
||||
return Buffer.from(JSON.stringify(obj)).toString('base64url');
|
||||
}
|
||||
|
||||
function makeJWT(role) {
|
||||
const h = b64url({alg:'HS256',typ:'JWT'});
|
||||
const p = b64url({role,iss:'supabase',iat:1700000000,exp:2000000000});
|
||||
const sig = crypto.createHmac('sha256',secret).update(h+'.'+p).digest('base64url');
|
||||
return h+'.'+p+'.'+sig;
|
||||
}
|
||||
|
||||
console.log('ANON_KEY='+makeJWT('anon'));
|
||||
console.log('SERVICE_ROLE_KEY='+makeJWT('service_role'));
|
||||
"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PARTE 3 — Configurar el servicio en Dokploy
|
||||
|
||||
### 3.1 Crear el servicio
|
||||
|
||||
1. Projects -> seleccionar proyecto -> Create Service -> Docker Compose.
|
||||
2. Provider: Gitea (o GitHub) -> seleccionar el repo del paso 1.4.
|
||||
3. Branch: master | Compose Path: ./docker-compose.yml
|
||||
4. IMPORTANTE: desactivar Autodeploy (toggle OFF).
|
||||
|
||||
### 3.2 Variables de entorno (pestaña Environment)
|
||||
|
||||
Pegar el bloque completo reemplazando los valores:
|
||||
|
||||
```env
|
||||
POSTGRES_PASSWORD=<openssl rand -hex 24>
|
||||
POSTGRES_HOST=db
|
||||
POSTGRES_DB=postgres
|
||||
POSTGRES_PORT=5432
|
||||
JWT_SECRET=<openssl rand -hex 40>
|
||||
JWT_EXPIRY=3600
|
||||
ANON_KEY=<JWT anon>
|
||||
SERVICE_ROLE_KEY=<JWT service_role>
|
||||
SUPABASE_ANON_KEY=<igual a ANON_KEY>
|
||||
SUPABASE_SERVICE_KEY=<igual a SERVICE_ROLE_KEY>
|
||||
DASHBOARD_USERNAME=admin
|
||||
DASHBOARD_PASSWORD=<openssl rand -hex 16>
|
||||
KONG_HTTP_PORT=8000
|
||||
KONG_HTTPS_PORT=8443
|
||||
SECRET_KEY_BASE=<openssl rand -hex 40>
|
||||
VAULT_ENC_KEY=<openssl rand -hex 20>
|
||||
PG_META_CRYPTO_KEY=<openssl rand -hex 24>
|
||||
SITE_URL=http://localhost:3000
|
||||
API_EXTERNAL_URL=http://localhost:8000
|
||||
SUPABASE_PUBLIC_URL=http://localhost:8000
|
||||
STUDIO_DEFAULT_ORGANIZATION=NombreOrg
|
||||
STUDIO_DEFAULT_PROJECT=NombreProyecto
|
||||
SMTP_ADMIN_EMAIL=admin@example.com
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=
|
||||
SMTP_PASS=
|
||||
SMTP_SENDER_NAME=
|
||||
ENABLE_EMAIL_SIGNUP=false
|
||||
ENABLE_EMAIL_AUTOCONFIRM=false
|
||||
ENABLE_PHONE_SIGNUP=false
|
||||
ENABLE_PHONE_AUTOCONFIRM=false
|
||||
DISABLE_SIGNUP=true
|
||||
ENABLE_ANONYMOUS_USERS=false
|
||||
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=false
|
||||
MAILER_URLPATHS_CONFIRMATION=/auth/v1/verify
|
||||
MAILER_URLPATHS_INVITE=/auth/v1/verify
|
||||
MAILER_URLPATHS_RECOVERY=/auth/v1/verify
|
||||
MAILER_URLPATHS_EMAIL_CHANGE=/auth/v1/verify
|
||||
ADDITIONAL_REDIRECT_URLS=
|
||||
POOLER_TENANT_ID=pooler
|
||||
POOLER_DEFAULT_POOL_SIZE=20
|
||||
POOLER_MAX_CLIENT_CONN=100
|
||||
POOLER_PROXY_PORT_TRANSACTION=6543
|
||||
POOLER_DB_POOL_SIZE=10
|
||||
STORAGE_TENANT_ID=default
|
||||
GLOBAL_S3_BUCKET=
|
||||
REGION=
|
||||
S3_PROTOCOL_ACCESS_KEY_ID=
|
||||
S3_PROTOCOL_ACCESS_KEY_SECRET=
|
||||
LOGFLARE_LOGGER_BACKEND_API_KEY=logflare-local-key
|
||||
LOGFLARE_API_KEY=logflare-local-key
|
||||
FUNCTIONS_VERIFY_JWT=true
|
||||
IMGPROXY_ENABLE_WEBP_DETECTION=true
|
||||
IMGPROXY_AUTO_WEBP=true
|
||||
PGRST_DB_SCHEMAS=public,storage,graphql_public
|
||||
```
|
||||
|
||||
### 3.3 Deploy y validacion
|
||||
|
||||
Click en Deploy. Cuando termine verificar:
|
||||
|
||||
```bash
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}" | grep supabase
|
||||
|
||||
docker exec supabase-db psql -U postgres -c 'SELECT version();'
|
||||
|
||||
curl -s http://localhost:8000/rest/v1/ -H "apikey: <ANON_KEY>" | head -c 200
|
||||
|
||||
docker exec supabase-db psql -U postgres -c '\l' | grep supabase
|
||||
```
|
||||
|
||||
Contenedores esperados healthy: supabase-db, supabase-kong, supabase-studio,
|
||||
supabase-auth, supabase-rest, supabase-storage, supabase-meta, supabase-pooler,
|
||||
supabase-imgproxy, realtime-dev.supabase-realtime.
|
||||
supabase-edge-functions puede quedar en Restarting si no se usa Edge Functions (no critico).
|
||||
|
||||
---
|
||||
|
||||
## PARTE 4 — Aplicar las migraciones del proyecto
|
||||
|
||||
El puerto 5432 no esta expuesto externamente. Las migraciones se aplican desde el VPS.
|
||||
|
||||
### 4.1 Copiar migraciones al VPS (desde la maquina de desarrollo)
|
||||
|
||||
```bash
|
||||
ssh administrator@<VPS_IP> "mkdir -p ~/migrations"
|
||||
scp -r supabase/migrations/ administrator@<VPS_IP>:~/migrations/
|
||||
```
|
||||
|
||||
### 4.2 Aplicar en orden
|
||||
|
||||
```bash
|
||||
for f in $(ls ~/migrations/*.sql | sort); do
|
||||
echo "Aplicando: $f"
|
||||
docker exec -i supabase-db psql -U postgres -d postgres < "$f"
|
||||
echo "---"
|
||||
done
|
||||
```
|
||||
|
||||
### 4.3 Verificar
|
||||
|
||||
```bash
|
||||
docker exec supabase-db psql -U postgres -d postgres -c \
|
||||
"SELECT tablename, policyname FROM pg_policies WHERE schemaname='public' ORDER BY tablename;"
|
||||
```
|
||||
|
||||
### Nota: auth.jwt() en GoTrue v2.189+
|
||||
|
||||
GoTrue v2.189+ elimino auth.jwt(). Las funciones disponibles son auth.uid(), auth.role().
|
||||
Las politicas que usen custom claims JWT (app_role, org_id) fallaran. Ver TECH-DEBT-007.
|
||||
No es bloqueante para el piloto porque el dashboard usa service_role que bypasa RLS.
|
||||
|
||||
---
|
||||
|
||||
## PARTE 5 — Backups locales
|
||||
|
||||
### 5.1 Crear el script
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/backups/supabase-prod
|
||||
sudo chmod 700 /var/backups/supabase-prod
|
||||
|
||||
sudo tee /usr/local/bin/supabase-backup.sh > /dev/null << 'SCRIPT'
|
||||
#!/usr/bin/bash
|
||||
set -euo pipefail
|
||||
TS=$(date +%Y%m%d_%H%M%S)
|
||||
OUT=/var/backups/supabase-prod/supabase_$TS.sql.gz
|
||||
docker exec supabase-db pg_dump -U postgres postgres | gzip > "$OUT"
|
||||
chmod 600 "$OUT"
|
||||
find /var/backups/supabase-prod -name "supabase_*.sql.gz" -mtime +30 -delete
|
||||
echo "Backup OK: $OUT ($(du -h "$OUT" | cut -f1))"
|
||||
SCRIPT
|
||||
|
||||
sudo chmod +x /usr/local/bin/supabase-backup.sh
|
||||
```
|
||||
|
||||
### 5.2 Probar y configurar cron
|
||||
|
||||
```bash
|
||||
sudo bash /usr/local/bin/supabase-backup.sh
|
||||
|
||||
sudo crontab -e
|
||||
# Agregar: 0 3 * * * /usr/bin/bash /usr/local/bin/supabase-backup.sh >> /var/log/supabase-backup.log 2>&1
|
||||
```
|
||||
|
||||
### 5.3 Probar restore (obligatorio)
|
||||
|
||||
```bash
|
||||
docker exec supabase-db createdb -U postgres restore_test
|
||||
gunzip < $(ls /var/backups/supabase-prod/*.sql.gz | tail -1) | \
|
||||
docker exec -i supabase-db psql -U postgres restore_test
|
||||
docker exec supabase-db psql -U postgres -d restore_test -c \
|
||||
"SELECT count(*) FROM pg_tables WHERE schemaname='public';"
|
||||
docker exec supabase-db dropdb -U postgres restore_test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PARTE 6 — Dominio y SSL (pendiente)
|
||||
|
||||
En Dokploy, pestaña Domains del servicio supabase:
|
||||
- Agregar dominio db.dominio.com -> apuntar a supabase-kong:8000 -> activar SSL.
|
||||
- Studio NO tiene dominio publico. Acceso solo via SSH tunnel:
|
||||
ssh -L 3001:localhost:3001 administrator@VPS_IP -> abrir http://localhost:3001
|
||||
|
||||
Actualizar variables de entorno:
|
||||
```
|
||||
API_EXTERNAL_URL=https://db.dominio.com
|
||||
SUPABASE_PUBLIC_URL=https://db.dominio.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PARTE 7 — Conectar la app Next.js
|
||||
|
||||
Actualizar variables de entorno de la app Next.js en Dokploy:
|
||||
```
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://db.dominio.com
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=<ANON_KEY>
|
||||
SUPABASE_SERVICE_ROLE_KEY=<SERVICE_ROLE_KEY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Errores conocidos y soluciones
|
||||
|
||||
| Error | Causa | Solucion |
|
||||
|---|---|---|
|
||||
| password authentication failed for supabase_storage_admin | POSTGRES_PASSWORD con caracteres especiales | Usar openssl rand -hex 24 |
|
||||
| database "_supabase" does not exist | volumes/db/ no accesible | Usar modo Git en Dokploy, no docker raw |
|
||||
| keyauth_credentials uniqueness violation | ANON_KEY = SERVICE_ROLE_KEY o duplicados en kong.yml | Una sola entrada por bloque en kong.yml |
|
||||
| exec: kong-entrypoint.sh is a directory | Volumen corrupto | docker volume rm del volumen afectado |
|
||||
| GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: converting to bool | Variable vacia | Agregar GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=false |
|
||||
| GOTRUE_SMTP_PORT: converting to int | SMTP_PORT vacio | Agregar SMTP_PORT=587 |
|
||||
| auth.jwt() does not exist | GoTrue v2.189+ lo elimino | Ver TECH-DEBT-007 |
|
||||
| Conflicto de nombres de contenedores | Stack manual y Dokploy corriendo en paralelo | docker compose down desde ~/supabase-project |
|
||||
|
||||
---
|
||||
|
||||
## Deuda tecnica
|
||||
|
||||
- TECH-DEBT-006: Backup remoto (S3) — bloqueante antes del primer dato real de empresa.
|
||||
- TECH-DEBT-007: Migrar auth.jwt() a alternativa compatible con GoTrue v2.189+.
|
||||
|
||||
---
|
||||
|
||||
## Datos operativos de esta instalacion
|
||||
|
||||
- VPS: inq-mart-2 - IP VPN: 172.20.5.11
|
||||
- Dokploy: https://172.20.5.11 (acceso via VPN)
|
||||
- Repo Supabase: https://git.inquality.com.py/marcos/supabase-motor-fomularios
|
||||
- Backups: /var/backups/supabase-prod/ (diarios, retencion 30 dias)
|
||||
- Studio: ssh -L 3001:localhost:3001 administrator@172.20.5.11 -> http://localhost:3001
|
||||
89
OBSERVATIONS.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# OBSERVATIONS.md — Observaciones y breaking changes
|
||||
|
||||
> Registro de cambios al instrumento que rompen compatibilidad con datos existentes
|
||||
> o que requieren atención del director antes/después de re-seedear. No bloquean el
|
||||
> trabajo del agente — se documentan para que el director decida qué hacer con los
|
||||
> datos ya capturados.
|
||||
|
||||
---
|
||||
|
||||
## Etapa 3I2 — Revisión del instrumento v2.1 (feedback equipo médico)
|
||||
|
||||
### 1. Eliminación de "Prefiero no decir" (Cambio 1)
|
||||
Afecta: A5, A6, 1.1, 1.5, 1.6, 1.13, 2.5, 3.1, 3.4.
|
||||
|
||||
Si existen respuestas guardadas con el valor `"Prefiero no decir"` en alguna de estas
|
||||
preguntas, esos valores quedan **huérfanos**: ya no aparecen como opción en el
|
||||
formulario ni en `seed.sql`, pero permanecen en `responses.answers` tal cual fueron
|
||||
guardados. No se borran ni se migran automáticamente.
|
||||
|
||||
**Impacto en triggers (1.1, 1.6, 1.13):** estas preguntas ahora tienen solo 2 opciones
|
||||
(Sí/No). La lógica condicional (incluyendo el nuevo salto-triple, Cambio 4a) solo
|
||||
reacciona a `"Sí"`. Una respuesta histórica `"Prefiero no decir"` en un trigger se
|
||||
trataría como "no Sí" a efectos de la lógica condicional si se re-evaluara, pero no
|
||||
hay re-evaluación retroactiva de respuestas ya enviadas.
|
||||
|
||||
### 2. 2.1 cambia de `single_choice` a `multiple_choice` (Cambio 5)
|
||||
Respuestas existentes para 2.1 están guardadas como un string único (`"Yo soy el/la
|
||||
principal"`, etc.). El formulario nuevo espera un array de strings para preguntas
|
||||
`multiple_choice`. Las respuestas históricas de 2.1 quedan en formato de string,
|
||||
inconsistente con el nuevo esquema esperado para esa pregunta.
|
||||
|
||||
Ver `TECH_DEBT.md` → TECH-DEBT-006 (deuda de ranking pendiente sobre esta misma
|
||||
pregunta).
|
||||
|
||||
### 3. Eliminación de la pregunta 2.4 (Cambio 6)
|
||||
"¿Contás con apoyo de otras personas para el cuidado?" se elimina del seed por ser
|
||||
similar/redundante con 2.1 (feedback del equipo médico).
|
||||
|
||||
El `DELETE FROM public.questions WHERE survey_id = ...` al inicio de `seed.sql` borra
|
||||
la fila de la pregunta 2.4 (id `40000000-0000-0000-0000-000000000027`). Si existen
|
||||
filas en `responses` cuyo `answers` referencian `question_id` = 2.4 (por
|
||||
`question_id`, no por código embebido en JSON — depende del esquema de `responses`),
|
||||
esas referencias quedan apuntando a un `question_id` que ya no existe en `questions`
|
||||
tras el re-seed.
|
||||
|
||||
**No re-seedear la base de producción sin que el director confirme que no hay
|
||||
respuestas reales que dependan de 2.4**, o decidir conservar la fila de 2.4 marcada
|
||||
como `archived`/inactiva en lugar de borrarla (cambio de diseño — Nivel 3, no
|
||||
ejecutado en esta etapa).
|
||||
|
||||
### 4. Cambio de texto en opción de 3.3 (Cambio 8)
|
||||
"Es difícil sostenerlo con mis ingresos" → "No puedo sostenerlo con mis ingresos".
|
||||
|
||||
Respuestas existentes que eligieron la opción anterior quedan guardadas con el texto
|
||||
literal `"Es difícil sostenerlo con mis ingresos"`, que ya no coincide con ninguna
|
||||
opción del seed actual. Cualquier agregación por valor exacto de opción debe
|
||||
contemplar ambos strings como equivalentes para no perder esos datos en reportes.
|
||||
|
||||
### 5. División de la primera franja salarial en 3.4 (Cambio 9)
|
||||
"Sueldo mínimo – Gs. 5.000.000" se reemplaza por dos opciones nuevas: "Sueldo mínimo"
|
||||
y "Gs. 3.000.000 a 5.000.000".
|
||||
|
||||
Respuestas existentes con el valor `"Sueldo mínimo – Gs. 5.000.000"` quedan con ese
|
||||
valor, que ya no existe como opción. No hay forma automática de reasignar esas
|
||||
respuestas a una de las dos franjas nuevas (la franja exacta del respondente no se
|
||||
puede inferir del valor agregado anterior).
|
||||
|
||||
### 6. Cambio de texto en última opción de 6.1 (Cambio 10)
|
||||
"No, nunca se ha hablado del tema" → "No, no se puede hablar del tema".
|
||||
|
||||
Respuestas existentes con el valor anterior quedan con ese texto literal, que ya no
|
||||
coincide con ninguna opción del seed actual.
|
||||
|
||||
---
|
||||
|
||||
## Resumen para el director
|
||||
|
||||
- Ningún cambio de esta etapa borra datos directamente, salvo el `DELETE` estándar de
|
||||
`questions` por `survey_id` (mecanismo de idempotencia ya existente en `seed.sql`,
|
||||
no nuevo de esta etapa) — pero ese `DELETE` ahora también elimina la fila de 2.4.
|
||||
- Los cambios 1, 8, 9 y 10 dejan **valores de texto huérfanos** en respuestas
|
||||
históricas (ya no matchean ninguna opción vigente). No afecta el funcionamiento del
|
||||
formulario hacia adelante, pero sí cualquier reporte/agregación que dependa de
|
||||
matchear el string exacto de la opción.
|
||||
- El cambio 5 (2.1) deja respuestas históricas en un formato (string) distinto al que
|
||||
el formulario nuevo produce (array). Si se construye un reporte sobre 2.1, debe
|
||||
manejar ambos formatos o normalizarse en una migración de datos aparte.
|
||||
- **No se ejecutó ningún re-seed sobre la base real** — esto aplica solo cuando el
|
||||
director apruebe re-seedear con `seed.sql` v2.1.
|
||||
205
SEED_SPEC.md
@@ -1,105 +1,152 @@
|
||||
# SEED_SPEC.md — Contrato del instrumento "Mapeo de cuidadores"
|
||||
# SEED_SPEC.md — Contrato del instrumento "Mapeo de Cuidadores" v2.1
|
||||
|
||||
> Inventario completo y autoritativo de la encuesta, extraído del mockup real
|
||||
> (`formulario_mapeo.html`). Reemplaza el seed inventado de la Etapa 1 (que tenía 9 de 34 preguntas y
|
||||
> el supuesto incorrecto C1→G4). Claude Code reescribe `seed.sql` a partir de ESTE contrato.
|
||||
> Versión 2.1 — incorpora feedback del equipo médico, Etapa 3I2 (Jun 2026). Reemplaza la v2.
|
||||
> 44 preguntas (se elimina 2.4, se agrega 6.6) · 8 secciones · Códigos únicos corregidos.
|
||||
> Claude Code reescribe `seed.sql` a partir de ESTE contrato.
|
||||
|
||||
## Convenciones (decisiones cerradas)
|
||||
- **Mapeo de tipos al esquema:** radio→`single_choice`, checkbox→`multiple_choice`, escala 1-5→
|
||||
`rating`, textarea→`text_long`.
|
||||
- **`conditional_rules` viven en la pregunta DEPENDIENTE** (cada pregunta condicional declara su
|
||||
propia condición de visibilidad: `{if_question, op:'eq', value, action:'show'}`). El motor, al
|
||||
decidir si renderiza X, mira las reglas de X. (Resuelve la ambigüedad que el agente había dejado.)
|
||||
- **B1/C1/D1 son `single_choice` de 3 opciones** (Sí / No / Prefiero no decir), **NO `yes_no`**.
|
||||
Solo el valor **"Sí"** dispara el bloque condicional; "No" y "Prefiero no decir" lo ocultan.
|
||||
- **`is_sensitive = true`** en toda pregunta que elicita salud/discapacidad/diagnóstico (marcadas
|
||||
abajo). La encuesta entera es `data_sensitivity = 'sensible'`.
|
||||
## Convenciones
|
||||
- Tipos: radio→`single_choice`, checkbox→`multiple_choice`, escala 1-5→`rating`,
|
||||
texto libre largo→`text_long`, texto corto→`text_short`.
|
||||
- **`conditional_rules` en la pregunta DEPENDIENTE** (cada pregunta declara su propia condición).
|
||||
- Los 3 triggers (1.1, 1.6, 1.13) son `single_choice` de 2 opciones (Sí · No); solo **"Sí"** dispara
|
||||
el bloque correspondiente.
|
||||
- `is_sensitive=true` en preguntas de salud/discapacidad/diagnóstico (marcadas ✅).
|
||||
- `3.4` (salary) — `is_sensitive=true` + analyst-only (ADR-001 addendum): nunca en vistas org_admin.
|
||||
- RE-INDEX: las preguntas marcadas son candidatas a índice compuesto (metodología TBD Sprint 3).
|
||||
- En preguntas `multiple_choice` el formulario muestra el subtítulo "Podés marcar más de una opción".
|
||||
- "Prefiero no decir" eliminada de todas las preguntas (Cambio 1, Etapa 3I2).
|
||||
|
||||
## Estructura condicional (3 bloques, el resto siempre visible)
|
||||
- **cond-B** — visible si `B1 = 'Sí'` → contiene B2, B3, B4, B5
|
||||
- **cond-C** — visible si `C1 = 'Sí'` → contiene C2, C3, C4, C5
|
||||
- **cond-D** — visible si `D1 = 'Sí'` → contiene D2, D3, D4, D5
|
||||
- Secciones A (datos generales), E (rol cuidador), F (económico), G (bienestar), H (necesidades):
|
||||
**top-level, siempre visibles.**
|
||||
## Estructura condicional (4 bloques)
|
||||
- **cond-disc-propia** — 1.1='Sí' → muestra 1.2, 1.3, 1.4, 1.5
|
||||
- **cond-familiar** — 1.6='Sí' → muestra 1.7, 1.8, 1.9, 1.10, 1.11, 1.12
|
||||
- **cond-adulto-mayor** — 1.13='Sí' → muestra 1.14, 1.15, 1.16
|
||||
- **salto-triple** — si 1.1='No' Y 1.6='No' Y 1.13='No' (es decir, ninguno de los tres dio
|
||||
'Sí'), se ocultan completas las secciones 2, 3, 4, 5 y las preguntas 6.1, 6.2, 6.3. El
|
||||
formulario salta directo a 6.4. Implementado como `conditional_rules`
|
||||
`{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}` en cada
|
||||
pregunta del bloque (equivalente: se muestra si AL MENOS uno de los tres es 'Sí').
|
||||
- Sección A, 6.4-6.6 y Sección 7 (cierre): **top-level, siempre visibles.**
|
||||
|
||||
---
|
||||
|
||||
## Sección 0 — Datos Generales (top-level, cuasi-identificadores)
|
||||
| Code | Tipo | Pregunta | Opciones |
|
||||
|---|---|---|---|
|
||||
| A1 | single_choice | ¿En qué área o gerencia trabajás? | Operaciones/Producción · Comercial/Ventas · Administración/Finanzas · RRHH/Cultura · Tecnología/Sistemas · Logística/Distribución · Otra área |
|
||||
| A2 | single_choice | ¿Cuántos años tenés? | Menos de 25 · 25-34 · 35-44 · 45-54 · 55+ · Prefiero no decir |
|
||||
| A3 | single_choice | ¿Con qué género te identificás? | Femenino · Masculino · No binario/Otro · Prefiero no decir |
|
||||
| A4 | single_choice | ¿Cuál es tu modalidad de trabajo actual? | Presencial · Remoto · Híbrido |
|
||||
## Sección A — Datos Generales (top-level)
|
||||
| Code | Tipo | Pregunta | Opciones | Notas |
|
||||
|---|---|---|---|---|
|
||||
| A1 | single_choice | ¿En qué departamento de Paraguay vivís?* | Asunción (Capital) · Alto Paraguay · Alto Paraná · Amambay · Boquerón · Caaguazú · Caazapá · Canindeyú · Central · Concepción · Cordillera · Guairá · Itapúa · Misiones · Ñeembucú · Paraguarí · Presidente Hayes · San Pedro | qi_zone |
|
||||
| A2 | single_choice | ¿En qué ciudad vivís?* | 219 ciudades (GeoSelectWidget: dependiente de A1) | qi_city — Sprint 3 |
|
||||
| A3 | text_short | ¿En qué barrio vivís? | — (texto libre, opcional) | |
|
||||
| A4 | single_choice | ¿En qué área o gerencia trabajás?* | Directorio · Gerencia · Operaciones/Producción · Comercial/Ventas · Administración/Finanzas · Recursos Humanos/Cultura · Tecnología/Sistemas · Logística/Distribución · Otra área | qi_sector |
|
||||
| A5 | single_choice | ¿Cuántos años tenés? | Menos de 25 años · 25 a 34 años · 35 a 44 años · 45 a 54 años · 55 años o más | qi_age_bucket |
|
||||
| A6 | single_choice | ¿Con qué género te identificás? | Femenino · Masculino · No binario/Otro | |
|
||||
| A7 | single_choice | ¿Cuál es tu modalidad de trabajo actual? | Presencial · Remoto (home office) · Híbrido | |
|
||||
|
||||
## Sección 1 — ¿Vivís con discapacidad? *(sensible)*
|
||||
## Sección 1 — Prevalencia: cuidado y discapacidad *(sensible)*
|
||||
| Code | Tipo | Cond. | Pregunta | Opciones | is_sensitive |
|
||||
|---|---|---|---|---|---|
|
||||
| B1 | single_choice | trigger | ¿Vos mismo/a tenés alguna discapacidad o condición de salud…? | Sí · No · Prefiero no decir | ✅ |
|
||||
| B2 | multiple_choice | B1=Sí | ¿Qué tipo de discapacidad tenés? (más de una) | Física/motriz · Visual · Auditiva · Intelectual · TEA · Psicosocial · Condición crónica · Otra | ✅ |
|
||||
| B3 | single_choice | B1=Sí | ¿Contás con certificado oficial de discapacidad? | Sí · En trámite · No | ✅ |
|
||||
| B4 | single_choice | B1=Sí | ¿Tu discapacidad afecta tu desempeño o comodidad en el trabajo? | Sí, significativamente · Sí, aspectos puntuales · No, bien gestionada · Prefiero no decir | ✅ |
|
||||
| B5 | single_choice | B1=Sí | ¿La empresa conoce tu situación? | Sí, con apoyo · Sí, sin apoyo · No, privado | ✅ |
|
||||
| 1.1 | single_choice | trigger | ¿Vos mismo/a vivís con alguna discapacidad o condición de salud que afecte tu funcionamiento cotidiano?* | Sí · No | ✅ |
|
||||
| 1.2 | multiple_choice | 1.1=Sí | ¿Qué tipo de discapacidad o condición tenés? | Discapacidad física/motriz · Visual · Auditiva · Intelectual · TEA · Psicosocial/salud mental · Condición crónica que genera limitaciones · Otra | ✅ |
|
||||
| 1.3 | multiple_choice | 1.1=Sí | ¿Usás algún elemento de apoyo para la movilidad o autonomía? | Silla de ruedas · Bastón · Andador · Muletas · Prótesis · Tecnología asistiva · No utilizo | ✅ |
|
||||
| 1.4 | single_choice | 1.1=Sí | ¿Contás con certificado oficial de discapacidad? | Sí · No, pero estoy en trámite · No | ✅ |
|
||||
| 1.5 | single_choice | 1.1=Sí | ¿Tu condición afecta tu desempeño o comodidad en el trabajo? | Sí, de forma significativa · Sí, pero en aspectos puntuales · No, está bien gestionada | ✅ |
|
||||
| 1.6 | single_choice | trigger | ¿Tenés algún familiar cercano (hijo/a, pareja, hermano/a, padre/madre) que viva con discapacidad?* | Sí · No | ✅ |
|
||||
| 1.7 | multiple_choice | 1.6=Sí | ¿Cuál es tu vínculo con esa persona? | Hijo/a · Cónyuge/pareja · Padre o madre · Hermano/a · Otro familiar | ✅ |
|
||||
| 1.8 | multiple_choice | 1.6=Sí | ¿Qué tipo de discapacidad tiene? | Física/motriz · Visual · Auditiva · Intelectual · TEA · Psicosocial/salud mental · Condición crónica severa · Otra | ✅ |
|
||||
| 1.9 | multiple_choice | 1.6=Sí | ¿Qué tipo de apoyo o acompañamiento necesita principalmente? | Movilidad física · Comunicación · Apoyo escolar/educativo · Inclusión laboral · Terapias/rehabilitación · Apoyo emocional/psicosocial | ✅ |
|
||||
| 1.10 | multiple_choice | 1.6=Sí | ¿Usa algún elemento de apoyo para la movilidad o autonomía? | Silla de ruedas · Bastón · Andador · Muletas · Prótesis · Tecnología asistiva · No utiliza | ✅ |
|
||||
| 1.11 | single_choice | 1.6=Sí | Si es niño/a o adolescente en edad escolar: ¿está escolarizado/a y enfrenta barreras de aprendizaje? | Escolarizado/a, sin barreras importantes · Escolarizado/a, enfrenta barreras o faltan ajustes · No está escolarizado/a · No aplica | ✅ |
|
||||
| 1.12 | single_choice | 1.6=Sí | ¿La persona con discapacidad se encuentra trabajando actualmente? | Sí, está trabajando · No, y está buscando empleo · No, y no está buscando · No aplica | ✅ |
|
||||
| 1.13 | single_choice | trigger | ¿Tenés algún adulto mayor (60+ años) a tu cuidado o bajo tu responsabilidad?* | Sí · No | |
|
||||
| 1.14 | single_choice | 1.13=Sí | ¿Qué nivel de dependencia tiene? (la más representativa) | Independiente, pero necesita supervisión · Dependencia leve · Dependencia moderada · Dependencia severa | ✅ |
|
||||
| 1.15 | multiple_choice | 1.13=Sí | ¿Tiene una condición de salud que genere mayor necesidad de cuidado? | Demencia/Alzheimer · ACV/Parálisis · Enfermedad crónica grave · Movilidad reducida · Deterioro cognitivo · Sin condición específica | ✅ |
|
||||
| 1.16 | single_choice | 1.13=Sí | ¿Vive con vos o en otra vivienda? | Vive en mi hogar · Vive en otra vivienda · Está en residencia/institución | |
|
||||
|
||||
## Sección 2 — Familiares con Discapacidad *(sensible)*
|
||||
| Code | Tipo | Cond. | Pregunta | Opciones | is_sensitive |
|
||||
|---|---|---|---|---|---|
|
||||
| C1 | single_choice | trigger | ¿Tenés algún familiar cercano que viva con discapacidad? | Sí · No · Prefiero no decir | ✅ |
|
||||
| C2 | multiple_choice | C1=Sí | ¿Cuál es el vínculo? (más de una) | Hijo/a · Cónyuge/pareja · Padre/madre · Hermano/a · Otro familiar | ✅ |
|
||||
| C3 | multiple_choice | C1=Sí | ¿Qué tipo de discapacidad tiene? (más de una) | Física/motriz · Visual · Auditiva · Intelectual · TEA · Psicosocial · Condición crónica | ✅ |
|
||||
| C4 | single_choice | C1=Sí | ¿Esta persona requiere apoyo o cuidado cotidiano? | Alta dependencia · Apoyo parcial · Autónomo/a | ✅ |
|
||||
| C5 | single_choice | C1=Sí | ¿Quién es el responsable principal del cuidado? | Yo principal · Compartido · Cuidador pagado · Institución | |
|
||||
## Sección 2 — Intensidad y rol del cuidado (top-level)
|
||||
> Cond.: **salto-triple** — toda la sección se oculta si 1.1='No' Y 1.6='No' Y 1.13='No'.
|
||||
|
||||
## Sección 3 — Adultos Mayores a Cargo *(sensible)*
|
||||
| Code | Tipo | Cond. | Pregunta | Opciones | is_sensitive |
|
||||
|---|---|---|---|---|---|
|
||||
| D1 | single_choice | trigger | ¿Tenés algún adulto mayor (60+) a tu cuidado? | Sí · No · Prefiero no decir | |
|
||||
| D2 | single_choice | D1=Sí | ¿Cuántos adultos mayores están bajo tu cuidado? | 1 · 2 · 3+ | |
|
||||
| D3 | single_choice | D1=Sí | ¿Qué nivel de dependencia tiene/n? | Supervisión · Dependencia leve · Dependencia moderada · Dependencia severa | ✅ |
|
||||
| D4 | multiple_choice | D1=Sí | ¿Tiene condición diagnosticada? (más de una) | Demencia/Alzheimer · ACV/Parálisis · Enf. crónica · Movilidad reducida · Deterioro cognitivo · Ninguna específica | ✅ |
|
||||
| D5 | single_choice | D1=Sí | ¿La persona vive con vos o en otra vivienda? | Vive conmigo · Otra vivienda · Institución | |
|
||||
|
||||
## Sección 4 — Tu Rol como Cuidador/a (top-level)
|
||||
| Code | Tipo | Pregunta | Opciones |
|
||||
|---|---|---|---|
|
||||
| E1 | single_choice | ¿Cuántas horas por día dedicás, en promedio, al cuidado directo? | Menos de 1h · 1-3h · 3-6h · Más de 6h · Variable |
|
||||
| E2 | multiple_choice | ¿En qué momentos realizás principalmente el cuidado? (más de una) | Antes del trabajo · Durante horario laboral · Al llegar · Durante la noche · Fines de semana · Continuo |
|
||||
| E3 | single_choice | ¿Contás con apoyo de otras personas para el cuidado? | Apoyo familiar suficiente · Apoyo insuficiente · Único responsable · Apoyo contratado |
|
||||
| E4 | single_choice | ¿Tuviste que faltar al trabajo por el cuidado en el último año? | Nunca · Pocas veces (1-3) · Varias veces (4-10) · Con frecuencia (10+) |
|
||||
| 2.1 | multiple_choice ⚠️ TECH-DEBT | ¿Quién es el/la responsable principal del cuidado? | Yo soy el/la principal · Comparto con otro familiar · Hay cuidador/a pagado/a · Está en institución/centro de día |
|
||||
| 2.2 | single_choice | ¿Cuántas horas por día dedicás, en promedio, al cuidado directo? | Menos de 1h · Entre 1 y 3h · Entre 3 y 6h · Más de 6h · Varía mucho |
|
||||
| 2.3 | multiple_choice | ¿En qué momentos del día cuidás principalmente? | Antes del trabajo · Durante horario laboral (llamadas/emergencias) · Al llegar del trabajo · Durante la noche · Los fines de semana · Es continuo sin horario fijo |
|
||||
| 2.5 | single_choice | Como cuidador/a primario, ¿presentás signos de agotamiento o estrés crónico? | Sí, frecuentemente · A veces · No, lo llevo bien |
|
||||
|
||||
> ⚠️ TECH-DEBT (2.1): pasó de `single_choice` a `multiple_choice` — selección múltiple sin orden
|
||||
> de prioridad. Deuda: implementar ranking (Principal cuidador soy yo > secundario instituto >
|
||||
> principal pagado > secundario familiar). Ver `TECH_DEBT.md`.
|
||||
>
|
||||
> **2.4 eliminada** (Cambio 6, Etapa 3I2): "¿Contás con apoyo de otras personas para el cuidado?"
|
||||
> — feedback del equipo médico: pregunta similar/redundante con 2.1. Ver `OBSERVATIONS.md`.
|
||||
|
||||
## Sección 3 — Impacto económico del cuidado (top-level)
|
||||
> Cond.: **salto-triple** — toda la sección se oculta si 1.1='No' Y 1.6='No' Y 1.13='No'.
|
||||
> 3.4: is_sensitive=true + analyst-only (no exponer a org_admin).
|
||||
|
||||
| Code | Tipo | Pregunta | Opciones | Notas |
|
||||
|---|---|---|---|---|
|
||||
| 3.1 | single_choice | ¿Cuánto estimás que gastás por mes en el cuidado? | Menos de Gs. 500.000 · Gs. 500.000–1.500.000 · Gs. 1.500.000–3.000.000 · Gs. 3.000.000–6.000.000 · Más de Gs. 6.000.000 | |
|
||||
| 3.2 | multiple_choice | ¿Qué servicios o apoyos utilizás actualmente? | Terapias especializadas · Cuidador/a particular · Centro de día/institución · Transporte adaptado · Medicación alto costo · Equipamiento médico/tecnología asistiva · No utilizo servicios formales | |
|
||||
| 3.3 | multiple_choice | ¿Cómo sostenés los gastos y responsabilidades del cuidado? | Con mi empleo de tiempo completo · Con mi empleo de tiempo parcial · Con trabajo independiente/emprendimiento · Me ayudan mis familiares · No puedo sostenerlo con mis ingresos | |
|
||||
| 3.4 | single_choice | ¿En qué franja salarial mensual te ubicás? *(CONFIDENCIAL — analyst-only)* | Sueldo mínimo · Gs. 3.000.000 a 5.000.000 · Gs. 5.000.001–8.000.000 · Gs. 8.000.001–12.000.000 · Gs. 12.000.001–15.000.000 · Gs. 15.000.001–18.000.000 · Gs. 18.000.001–25.000.000 · Gs. 25.000.001–50.000.000 · Gs. 50.000.001 o más | is_sensitive=true; never in org_admin views |
|
||||
| 3.5 | rating | ¿Los gastos de cuidado impactan en tu situación económica personal? | 1=Sin impacto · 2=Leve · 3=Moderado · 4=Alto · 5=Muy alto | RE-INDEX |
|
||||
|
||||
> **Cambio 7** (3.1): se elimina "Sin gastos significativos" además de "Prefiero no decir".
|
||||
> **Cambio 8** (3.3): "Es difícil sostenerlo con mis ingresos" → "No puedo sostenerlo con mis
|
||||
> ingresos". Breaking change para datos existentes — ver `OBSERVATIONS.md`.
|
||||
> **Cambio 9** (3.4): "Sueldo mínimo – Gs. 5.000.000" se divide en "Sueldo mínimo" y
|
||||
> "Gs. 3.000.000 a 5.000.000". Breaking change para datos existentes — ver `OBSERVATIONS.md`.
|
||||
|
||||
## Sección 4 — Tiempo y descanso (top-level)
|
||||
> Cond.: **salto-triple** — toda la sección se oculta si 1.1='No' Y 1.6='No' Y 1.13='No'.
|
||||
|
||||
## Sección 5 — Impacto Económico (top-level)
|
||||
| Code | Tipo | Pregunta | Opciones |
|
||||
|---|---|---|---|
|
||||
| F1 | single_choice | ¿Cuánto estimás que gastás mensualmente en el cuidado? | Sin gastos significativos · Menos de 500k · 500k-1.5M · 1.5M-3M · 3M-6M · Más de 6M · Prefiero no decir |
|
||||
| F2 | multiple_choice | ¿Qué servicios o apoyos utilizás actualmente? (más de una) | Terapias especializadas · Cuidador particular · Centro de día · Transporte adaptado · Medicación alto costo · Tecnología asistiva · Apoyo ONG · Sin servicios formales |
|
||||
| F3 | rating | ¿Los gastos de cuidado impactan en tu situación económica personal? | Escala 1 (Sin impacto) → 5 (Muy alto) |
|
||||
| 4.1 | single_choice | ¿Cómo describirías tu nivel de descanso habitual? | Descanso bien, me siento recuperado/a · Descanso de forma irregular, pero lo manejo · Me cuesta descansar, me siento cansado/a con frecuencia · Descanso muy poco, el agotamiento afecta mi día a día | RE-INDEX |
|
||||
| 4.2 | multiple_choice | ¿Qué aspectos de tu vida se ven más afectados por tu rol de cuidador/a? | Tiempo libre y ocio · Sueño y descanso · Vida social y amistades · Vida en pareja/familiar · Salud física personal · Salud emocional/mental · Desarrollo profesional · Situación económica | |
|
||||
| 4.3 | rating | En una escala del 1 al 5, ¿cómo evaluás tu bienestar general en este momento? | 1=Muy mal · 2=Mal · 3=Regular · 4=Bien · 5=Muy bien | RE-INDEX |
|
||||
|
||||
## Sección 5 — Impacto en el trabajo (top-level)
|
||||
> Cond.: **salto-triple** — toda la sección se oculta si 1.1='No' Y 1.6='No' Y 1.13='No'.
|
||||
|
||||
## Sección 6 — Bienestar y Descanso (top-level)
|
||||
| Code | Tipo | Pregunta | Opciones |
|
||||
|---|---|---|---|
|
||||
| G1 | single_choice | ¿Cómo describirías tu nivel de descanso habitual? | Descanso bien · Descanso irregular · Me cuesta descansar · Muy poco descanso |
|
||||
| G2 | single_choice | En los últimos 3 meses, ¿con qué frecuencia el cuidado afectó tu concentración o rendimiento laboral? | Nunca/casi nunca · Algunas veces al mes · Varias veces por semana · Casi todos los días |
|
||||
| G3 | multiple_choice | ¿Qué aspectos de tu vida se ven más afectados? (más de una) | Tiempo libre · Sueño y descanso · Vida social · Vida en pareja/familia · Salud física · Salud emocional · Desarrollo profesional · Situación económica |
|
||||
| G4 | rating | ¿Cómo evaluás tu bienestar general en este momento? | Escala 1 (Muy mal) → 5 (Muy bien) |
|
||||
| G5 | single_choice | ¿Sentís que tu empresa comprende la realidad de ser cuidador/a? | Sí, acompañado/a · Parcialmente · No, invisible · Nunca lo supo |
|
||||
| 5.1 | single_choice | En los últimos 3 meses, ¿con qué frecuencia sentiste que el cuidado afectó tu concentración o rendimiento? | Nunca o casi nunca · Algunas veces al mes · Varias veces por semana · Casi todos los días | RE-INDEX |
|
||||
| 5.2 | single_choice | En el último año, ¿faltaste al trabajo por situaciones relacionadas al cuidado? | Nunca · Pocas veces (1–3 veces al año) · Varias veces (4–10 veces al año) · Con frecuencia (más de 10 veces al año) | |
|
||||
|
||||
## Sección 7 — Necesidades (top-level)
|
||||
| Code | Tipo | Pregunta | Opciones / nota |
|
||||
## Sección 6 — Conciencia organizacional (top-level)
|
||||
> Cond. (6.1-6.3): **salto-triple** — se ocultan si 1.1='No' Y 1.6='No' Y 1.13='No'.
|
||||
> 6.4-6.6: siempre visibles (destino del salto).
|
||||
|
||||
| Code | Tipo | Pregunta | Opciones | Notas |
|
||||
|---|---|---|---|---|
|
||||
| 6.1 | single_choice | ¿Sentís que tu empresa conoce o comprende la realidad del cuidado y la discapacidad de su gente?* | Sí, me siento acompañado/a · Parcialmente, hay cierta comprensión · No, es algo bastante invisible · No, no se puede hablar del tema | RE-INDEX. Cambio 10: última opción reescrita (era "No, nunca se ha hablado del tema"). Breaking change — ver `OBSERVATIONS.md`. |
|
||||
| 6.2 | multiple_choice (**max_select=3**) | ¿Qué apoyos de la empresa te ayudarían más? Elegí hasta 3 | Flexibilidad horaria o trabajo remoto · Apoyo económico o convenios/descuentos · Acceso a terapias o salud · Contención/apoyo emocional o psicológico · Capacitación y autocuidado · Redes de apoyo/grupos de pares · Información sobre recursos y servicios disponibles | ⚠️ Ranking → multiple_choice para v1. Ranking widget al backlog. |
|
||||
| 6.3 | single_choice | ¿Estarías dispuesto/a a participar en un grupo de apoyo para cuidadores dentro de la empresa? | Sí, definitivamente · Tal vez, si hay más personas en la misma situación · No por ahora · Prefiero mantenerlo en privado | |
|
||||
| 6.4 | single_choice | ¿Conocés a algún compañero/a de trabajo que esté al cuidado de una persona con discapacidad o dependiente? | Sí, conozco a varias personas · Sí, conozco a una persona · No conozco a nadie · No estoy seguro/a | Código corregido (era 6.2 duplicado) |
|
||||
| 6.5 | single_choice | ¿Considerás que la empresa debería ofrecer apoyos a quienes cuidan a una persona dependiente? | Sí, definitivamente · Tal vez, dependería del caso · No lo considero necesario · No lo sé | Código corregido (era 6.3 duplicado) |
|
||||
| 6.6 | single_choice | ¿Estarías dispuesto/a a ser voluntario/a en iniciativas y/o redes de apoyo a personas con discapacidad y/o cuidadores? | Sí · No · Tal vez | Nueva (Cambio 4b, Etapa 3I2) |
|
||||
|
||||
## Sección 7 — Para cerrar (top-level)
|
||||
| Code | Tipo | Pregunta | Notas |
|
||||
|---|---|---|---|
|
||||
| H1 | multiple_choice (**max_select=3**) | ¿Qué tipo de apoyo de tu empresa te ayudaría más? (hasta 3) | Flexibilidad horaria/remoto · Info recursos y servicios · Apoyo psicológico · Espacio de escucha · Convenios descuentos · Grupos de pares · Capacitación autocuidado · Nada, estoy bien |
|
||||
| H2 | single_choice | ¿Estarías dispuesto/a a participar en un grupo de apoyo para cuidadores? | Sí, definitivamente · Tal vez · No por ahora · Prefiero privacidad |
|
||||
| H3 | text_long | ¿Querés compartir algo más sobre tu experiencia? (opcional) | Texto libre, opcional. **No entra al store analítico crudo** (regla 4 CLAUDE.md). |
|
||||
| 7.1 | text_long | ¿Te gustaría dejar un comentario o sugerencia especial? (opcional) | No entra al store analítico crudo (regla 4 CLAUDE.md) |
|
||||
|
||||
---
|
||||
|
||||
## Mapeo a cuasi-identificadores (`responses.qi_*`)
|
||||
- `qi_age_bucket` ← **A2** (ya viene en bucket — bien).
|
||||
- `qi_sector` ← **A1** (área).
|
||||
- `qi_zone` ← **no aplica en este instrumento** (no se pregunta zona geográfica; es intra-empresa). Va NULL.
|
||||
- `qi_age_bucket` ← A5 (ya en bucket)
|
||||
- `qi_sector` ← A4 (área)
|
||||
- `qi_zone` ← A1 (departamento — poblado en `submit-survey.ts` desde Etapa 2E)
|
||||
- `qi_city` ← A2: **Sprint 3** — se agrega como columna qi en migration futura.
|
||||
Por ahora A2 se captura en `answers` como single_choice.
|
||||
|
||||
## ⚠️ Hallazgo para el director (decisión de modelo de datos, Nivel 3 — NO ejecutar)
|
||||
El dashboard original quería filtrar por **género (A3)** y **modalidad (A4)**, pero `agg_segment`
|
||||
solo tiene en su whitelist `qi_age_bucket / qi_sector / qi_zone`. Para segmentar/filtrar por género o
|
||||
modalidad en el dashboard (Sprint 3), hay que decidir: ¿se agregan columnas `qi_gender` / `qi_modality`
|
||||
(y a la whitelist de `agg_segment`), o se resuelven por otra vía? Es decisión de esquema → consultar
|
||||
antes de Sprint 3, no resolver en el seed.
|
||||
## ⚠️ Decisiones pendientes para el director (NO ejecutar sin aprobación)
|
||||
- **Ranking widget (6.2):** actual es multiple_choice max_select=3. Cuando se implemente el
|
||||
widget de ranking (drag o 3 dropdowns), 6.2 pasa a tipo `ranking` con JSONB `{rank1, rank2, rank3}`.
|
||||
- **qi_city:** migration futura para agregar columna en responses y extender whitelist de agg_segment.
|
||||
Se hace antes de Sprint 3 (dashboard con filtro por ciudad).
|
||||
- **Salary analytics:** metodología para calcular "carga económica del cuidado como % del ingreso"
|
||||
es trabajo de metodología de investigación (fuera del scope del código).
|
||||
- **RE-INDEX scoring:** algoritmo de composición de los índices (descanso, bienestar, rendimiento,
|
||||
conciencia) — decisión antes de construir los reportes del dashboard.
|
||||
- **Formulario de empresa (Relevamiento Empresa):** instrumento separado con PII de contacto.
|
||||
Ejecutar offline para las primeras empresas. Diseño del modelo de datos en el sprint del builder.
|
||||
87
TECH_DEBT.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# TECH_DEBT.md — Deuda técnica registrada
|
||||
|
||||
> Ítems identificados durante el desarrollo. Cada ítem documenta qué falta, por qué
|
||||
> no se hizo ahora, y qué trigger habilitaría su resolución.
|
||||
|
||||
---
|
||||
|
||||
## [TECH-DEBT-001] Ranking widget para 6.2
|
||||
|
||||
- **Estado:** 6.2 es actualmente `multiple_choice` max_select=3. El widget muestra
|
||||
frecuencia simple (top 8).
|
||||
- **Deuda:** cuando el tipo pase a `ranking` con JSONB `{rank1, rank2, rank3}`, el
|
||||
widget debe reescribirse para mostrar ponderación por posición.
|
||||
- **Trigger:** decisión del director sobre el tipo de 6.2 en próxima versión del
|
||||
instrumento (anotada en SEED_SPEC.md § Decisiones pendientes).
|
||||
|
||||
---
|
||||
|
||||
## [TECH-DEBT-002] KPI NPS Cuidador
|
||||
|
||||
- **Estado:** el instrumento actual no tiene pregunta NPS. El KPI fue eliminado del
|
||||
dashboard en Etapa 2E.
|
||||
- **Para implementar:** agregar en próxima versión del instrumento:
|
||||
"En una escala de 0 a 10, ¿qué tan probable es que recomiendes esta empresa a otra
|
||||
persona cuidadora?"
|
||||
- **Trigger:** decisión de incluir NPS en el instrumento v2.
|
||||
|
||||
---
|
||||
|
||||
## [TECH-DEBT-003] Brecha de políticas (oferta vs. demanda)
|
||||
|
||||
- **Estado:** el instrumento captura solo demanda (6.2 = apoyos que ayudarían).
|
||||
No captura qué políticas la empresa ya ofrece. W6 muestra solo demanda.
|
||||
- **Para implementar:** agregar en próxima versión del instrumento:
|
||||
"¿Qué apoyos para el cuidado ofrece tu empresa hoy?" (multi-select con las mismas
|
||||
opciones de 6.2 + "Ninguno" + "No lo sé").
|
||||
- **Trigger:** decisión de agregar la pregunta de oferta al instrumento.
|
||||
|
||||
---
|
||||
|
||||
## [TECH-DEBT-004] Intención directa de salida laboral
|
||||
|
||||
- **Estado:** el instrumento no pregunta directamente por intención de dejar el
|
||||
empleo. W8 usa indicador compuesto (4.3 + 5.1 + 5.2) renombrado a "Saturación".
|
||||
- **Para implementar:** agregar en próxima versión:
|
||||
"En los últimos 6 meses, ¿pensaste en dejar este empleo por razones relacionadas
|
||||
al cuidado?" (Sí/Frecuentemente · Sí/A veces · No · Prefiero no decir).
|
||||
- **Trigger:** validación metodológica de que el indicador compuesto no es suficiente
|
||||
para medir intención de salida.
|
||||
|
||||
---
|
||||
|
||||
## [TECH-DEBT-005] qi_city / qi_country — migración pendiente
|
||||
|
||||
- **Estado:** A1 (Departamento) y A2 (Ciudad) se guardan en `answers`. Las columnas
|
||||
`qi_city` y `qi_country` en `responses` son NULL. Sprint 3 las necesita para
|
||||
segmentación avanzada.
|
||||
- **Para implementar:** migration futura que agregue extracción de A1/A2 en el insert
|
||||
y extienda la whitelist de `agg_segment`.
|
||||
- **Trigger:** inicio de Sprint 3 (dashboard avanzado con filtros de ciudad).
|
||||
|
||||
---
|
||||
|
||||
## [TECH-DEBT-006] Ranking widget para 2.1
|
||||
|
||||
- **Estado:** Etapa 3I2 (Cambio 5) cambió 2.1 ("¿Quién es el/la responsable principal
|
||||
del cuidado?") de `single_choice` a `multiple_choice`. Permite selección múltiple
|
||||
pero sin orden de prioridad — pierde la noción de "responsable principal" cuando
|
||||
el respondente marca más de una opción.
|
||||
- **Deuda:** implementar widget de ranking para 2.1 con el orden:
|
||||
Principal cuidador soy yo > secundario instituto > principal pagado > secundario
|
||||
familiar (mismo patrón que TECH-DEBT-001 para 6.2).
|
||||
- **Trigger:** decisión del director sobre el tipo de 2.1 en próxima versión del
|
||||
instrumento.
|
||||
|
||||
---
|
||||
|
||||
## [TECH-DEBT-008] /admin/responses sin autenticación
|
||||
|
||||
- **Estado:** ABIERTO.
|
||||
- **Descripción:** la vista de control `/admin/responses` no tiene auth. Por diseño
|
||||
explícito, excluye preguntas `is_sensitive=true` (14 preguntas de salud/
|
||||
discapacidad) y `text_long`. Para mostrar esas preguntas en la vista, primero
|
||||
implementar un gate de autenticación para staff (Basic Auth con env var como
|
||||
mínimo viable).
|
||||
- **Impacto actual:** ninguno en producción — la URL no está publicada.
|
||||
- **Bloqueante para:** ampliar visibilidad de datos en la vista de control.
|
||||
111
app/actions/submit-survey.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
'use server'
|
||||
import { headers } from 'next/headers'
|
||||
import { createHash, randomUUID } from 'crypto'
|
||||
import { createClient as createSupabaseClient } from '@supabase/supabase-js'
|
||||
|
||||
export type SubmitResult =
|
||||
| { success: true }
|
||||
| { success: false; error: string }
|
||||
|
||||
export async function submitSurvey(payload: {
|
||||
surveyId: string
|
||||
answers: Record<string, unknown>
|
||||
questionMap: Record<string, string> // código → question_id UUID
|
||||
consentOperativo: boolean
|
||||
consentMacroN1: boolean
|
||||
}): Promise<SubmitResult> {
|
||||
// 1. ip_hash: server-only, nunca en texto plano
|
||||
const headersList = await headers()
|
||||
const rawIp =
|
||||
headersList.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'
|
||||
const salt = process.env.IP_HASH_SALT ?? ''
|
||||
const ipHash = createHash('sha256').update(rawIp + salt).digest('hex')
|
||||
|
||||
// 2. Cliente Supabase con anon key (RLS policies permiten INSERT a anon)
|
||||
// NUNCA usar service_role — el anon key respeta RLS
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL ?? ''
|
||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? ''
|
||||
const supabase = createSupabaseClient(supabaseUrl, supabaseAnonKey)
|
||||
|
||||
// 3. Leer survey para obtener org_id y consent_version_id
|
||||
const { data: survey, error: sErr } = await supabase
|
||||
.from('surveys')
|
||||
.select('org_id, consent_version_id')
|
||||
.eq('id', payload.surveyId)
|
||||
.single()
|
||||
|
||||
if (sErr || !survey) return { success: false, error: 'survey_not_found' }
|
||||
|
||||
// 4. Pre-generar UUIDs server-side para evitar .select().single() post-INSERT.
|
||||
//
|
||||
// DIAGNÓSTICO: el Supabase JS client usa RETURNING * en .select().single(), lo que
|
||||
// requiere SELECT policy para anon en consent_records/responses. Esa policy no existe
|
||||
// (correcto — anon no debe leer consent_records de otros respondentes).
|
||||
// Solución: pre-generar UUIDs con randomUUID() y NO hacer SELECT after INSERT.
|
||||
const consentId = randomUUID()
|
||||
const responseId = randomUUID()
|
||||
|
||||
// 5. INSERT consent_record (inmutable, ADR-003)
|
||||
// Sin .select() para no activar RETURNING y evitar el error RLS 42501.
|
||||
const { error: cErr } = await supabase
|
||||
.from('consent_records')
|
||||
.insert({
|
||||
id: consentId,
|
||||
consent_version_id: survey.consent_version_id,
|
||||
granted_scopes: {
|
||||
operativo: payload.consentOperativo,
|
||||
macro_n1: payload.consentMacroN1,
|
||||
},
|
||||
granted_at: new Date().toISOString(),
|
||||
})
|
||||
|
||||
if (cErr) return { success: false, error: `consent_insert_failed: ${cErr.message}` }
|
||||
|
||||
// 6. Extraer qi_* de las respuestas
|
||||
const qi_age_bucket =
|
||||
typeof payload.answers['A5'] === 'string' ? payload.answers['A5'] : null
|
||||
const qi_sector =
|
||||
typeof payload.answers['A4'] === 'string' ? payload.answers['A4'] : null
|
||||
const qi_zone =
|
||||
typeof payload.answers['A1'] === 'string' ? payload.answers['A1'] : null
|
||||
|
||||
// 7. INSERT response (respondent_id=null — encuesta anónima, regla 2 CLAUDE.md)
|
||||
const { error: rErr } = await supabase
|
||||
.from('responses')
|
||||
.insert({
|
||||
id: responseId,
|
||||
survey_id: payload.surveyId,
|
||||
company_id: survey.org_id,
|
||||
respondent_id: null,
|
||||
ip_hash: ipHash,
|
||||
consent_record_id: consentId,
|
||||
qi_age_bucket: qi_age_bucket || null,
|
||||
qi_sector: qi_sector || null,
|
||||
qi_zone: qi_zone || null,
|
||||
})
|
||||
|
||||
if (rErr) return { success: false, error: `response_insert_failed: ${rErr.message}` }
|
||||
|
||||
// 8. INSERT answers — una fila por pregunta respondida
|
||||
const answerRows = Object.entries(payload.answers)
|
||||
.map(([code, value]) => ({
|
||||
response_id: responseId,
|
||||
question_id: payload.questionMap[code],
|
||||
value,
|
||||
}))
|
||||
.filter(
|
||||
(row) =>
|
||||
row.question_id &&
|
||||
row.value !== null &&
|
||||
row.value !== undefined &&
|
||||
row.value !== '' &&
|
||||
!(Array.isArray(row.value) && row.value.length === 0)
|
||||
)
|
||||
|
||||
if (answerRows.length > 0) {
|
||||
const { error: aErr } = await supabase.from('answers').insert(answerRows)
|
||||
if (aErr) return { success: false, error: `answers_insert_failed: ${aErr.message}` }
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
}
|
||||
228
app/admin/responses/page.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { ResponsesTable, type DisplayRow } from './responses-table'
|
||||
|
||||
// Server Component sin auth — herramienta interna de diagnóstico (Etapa 3V-B).
|
||||
// Usa SUPABASE_SERVICE_ROLE_KEY: vive solo acá, server-side, nunca al cliente.
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const MAX_SUBMISSIONS = 500
|
||||
const PROMPT_MAX_LEN = 60
|
||||
|
||||
const SURVEY_OPTIONS = [
|
||||
{ label: 'Todos', id: null },
|
||||
{ label: 'InQuality', id: '30000000-0000-0000-0000-000000000002' },
|
||||
{ label: 'Fundación Solidaridad', id: '30000000-0000-0000-0000-000000000003' },
|
||||
{ label: 'Demo', id: '30000000-0000-0000-0000-000000000001' },
|
||||
] as const
|
||||
|
||||
interface AllowedQuestion {
|
||||
id: string
|
||||
code: string
|
||||
prompt: string
|
||||
type: string
|
||||
position: number
|
||||
}
|
||||
|
||||
interface AnswerRow {
|
||||
value: unknown
|
||||
question_id: string
|
||||
}
|
||||
|
||||
interface ResponseRow {
|
||||
id: string
|
||||
submitted_at: string
|
||||
qi_zone: string | null
|
||||
qi_sector: string | null
|
||||
answers: AnswerRow[]
|
||||
}
|
||||
|
||||
function getSupabaseAdmin() {
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://placeholder.supabase.co'
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY || 'placeholder'
|
||||
return createClient(url, serviceKey, { auth: { persistSession: false } })
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
const d = new Date(iso)
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
}
|
||||
|
||||
function truncate(text: string, max = PROMPT_MAX_LEN): string {
|
||||
return text.length > max ? `${text.slice(0, max)}...` : text
|
||||
}
|
||||
|
||||
// Spec 4.3 — null/undefined → null (renderizado como badge "sin dato")
|
||||
function renderValue(value: unknown, type: string): string | null {
|
||||
if (value === null || value === undefined) return null
|
||||
|
||||
switch (type) {
|
||||
case 'single_choice':
|
||||
case 'text_short':
|
||||
return String(value).replace(/^"|"$/g, '')
|
||||
case 'multiple_choice':
|
||||
if (Array.isArray(value)) return value.join(', ')
|
||||
return String(value)
|
||||
case 'rating':
|
||||
return String(value)
|
||||
default:
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
export default async function AdminResponsesPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ survey_id?: string }>
|
||||
}) {
|
||||
const { survey_id: surveyId } = await searchParams
|
||||
const supabase = getSupabaseAdmin()
|
||||
|
||||
// 1. Preguntas permitidas — text_long e is_sensitive=true se excluyen ACÁ,
|
||||
// antes de tocar la tabla answers. Sus valores nunca se consultan.
|
||||
const { data: questionsData, error: questionsError } = await supabase
|
||||
.from('questions')
|
||||
.select('id, code, prompt, type, position')
|
||||
.eq('is_sensitive', false)
|
||||
.neq('type', 'text_long')
|
||||
.order('position', { ascending: true })
|
||||
|
||||
if (questionsError) {
|
||||
return (
|
||||
<div className="admin-page" role="main">
|
||||
<h1 className="admin-title">Control de respuestas</h1>
|
||||
<p className="admin-error">No fue posible conectarse a la base de datos.</p>
|
||||
<pre className="admin-error-detail">{questionsError.message}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const allowedQuestions = (questionsData ?? []) as AllowedQuestion[]
|
||||
const allowedIds = allowedQuestions.map((q) => q.id)
|
||||
const questionMap = new Map(allowedQuestions.map((q) => [q.id, q]))
|
||||
|
||||
// 2. Submissions con sus respuestas — answers!inner + .in() filtra el ARRAY
|
||||
// embebido a solo question_id permitidos (consulta, no post-filtro).
|
||||
let responsesQuery = supabase
|
||||
.from('responses')
|
||||
.select(
|
||||
`
|
||||
id,
|
||||
submitted_at,
|
||||
qi_zone,
|
||||
qi_sector,
|
||||
answers!inner ( value, question_id )
|
||||
`
|
||||
)
|
||||
.in('answers.question_id', allowedIds)
|
||||
|
||||
if (surveyId) {
|
||||
responsesQuery = responsesQuery.eq('survey_id', surveyId)
|
||||
}
|
||||
|
||||
const { data: responsesData, error: responsesError } =
|
||||
allowedIds.length > 0
|
||||
? await responsesQuery.order('submitted_at', { ascending: false }).limit(MAX_SUBMISSIONS)
|
||||
: { data: [] as ResponseRow[], error: null }
|
||||
|
||||
if (responsesError) {
|
||||
return (
|
||||
<div className="admin-page" role="main">
|
||||
<h1 className="admin-title">Control de respuestas</h1>
|
||||
<p className="admin-error">No fue posible conectarse a la base de datos.</p>
|
||||
<pre className="admin-error-detail">{responsesError.message}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const responses = (responsesData ?? []) as unknown as ResponseRow[]
|
||||
|
||||
// 3. Contadores totales (independientes del límite de 500 submissions),
|
||||
// filtrados por survey_id cuando hay filtro activo.
|
||||
let submissionsCountQuery = supabase
|
||||
.from('responses')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
|
||||
if (surveyId) {
|
||||
submissionsCountQuery = submissionsCountQuery.eq('survey_id', surveyId)
|
||||
}
|
||||
|
||||
const { count: totalSubmissions } = await submissionsCountQuery
|
||||
|
||||
let answersCountQuery = supabase
|
||||
.from('answers')
|
||||
.select('*, responses!inner(survey_id)', { count: 'exact', head: true })
|
||||
.in('question_id', allowedIds)
|
||||
|
||||
if (surveyId) {
|
||||
answersCountQuery = answersCountQuery.eq('responses.survey_id', surveyId)
|
||||
}
|
||||
|
||||
const { count: totalAnswers } =
|
||||
allowedIds.length > 0 ? await answersCountQuery : { count: 0 }
|
||||
|
||||
// 4. Aplanar: una fila por answer, ordenadas por position dentro de cada submission
|
||||
const rows: DisplayRow[] = []
|
||||
responses.forEach((response, idx) => {
|
||||
const answers = response.answers
|
||||
.filter((a) => questionMap.has(a.question_id))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
questionMap.get(a.question_id)!.position - questionMap.get(b.question_id)!.position
|
||||
)
|
||||
|
||||
answers.forEach((a, ai) => {
|
||||
const q = questionMap.get(a.question_id)!
|
||||
const isFirstInGroup = ai === 0
|
||||
rows.push({
|
||||
key: `${response.id}-${a.question_id}`,
|
||||
submissionNum: idx + 1,
|
||||
isFirstInGroup,
|
||||
date: isFirstInGroup ? formatDate(response.submitted_at) : '',
|
||||
code: q.code,
|
||||
prompt: truncate(q.prompt),
|
||||
rendered: renderValue(a.value, q.type),
|
||||
zone: isFirstInGroup ? (response.qi_zone ?? '—') : '',
|
||||
sector: isFirstInGroup ? (response.qi_sector ?? '—') : '',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="admin-page" role="main">
|
||||
<header className="admin-header">
|
||||
<div>
|
||||
<h1 className="admin-title">Control de respuestas</h1>
|
||||
<p className="admin-subtitle">
|
||||
{totalSubmissions ?? 0} submissions · {totalAnswers ?? 0} respuestas individuales ·
|
||||
actualizado {formatDate(new Date().toISOString())}
|
||||
</p>
|
||||
<nav className="admin-survey-nav" aria-label="Selección de empresa">
|
||||
{SURVEY_OPTIONS.map((opt) => (
|
||||
<a
|
||||
key={opt.label}
|
||||
href={opt.id ? `/admin/responses?survey_id=${opt.id}` : '/admin/responses'}
|
||||
className={
|
||||
opt.id === (surveyId ?? null)
|
||||
? 'admin-survey-link admin-survey-link-active'
|
||||
: 'admin-survey-link'
|
||||
}
|
||||
aria-current={opt.id === (surveyId ?? null) ? 'page' : undefined}
|
||||
>
|
||||
{opt.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
<a href="/admin/responses" className="admin-refresh">Actualizar</a>
|
||||
</header>
|
||||
|
||||
<div className="admin-table-wrapper">
|
||||
<ResponsesTable rows={rows} />
|
||||
{rows.length === 0 && (
|
||||
<p className="admin-empty">Sin respuestas registradas todavía.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
98
app/admin/responses/responses-table.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export interface DisplayRow {
|
||||
key: string
|
||||
submissionNum: number
|
||||
isFirstInGroup: boolean
|
||||
date: string
|
||||
code: string
|
||||
prompt: string
|
||||
rendered: string | null
|
||||
zone: string
|
||||
sector: string
|
||||
}
|
||||
|
||||
export function ResponsesTable({ rows }: { rows: DisplayRow[] }) {
|
||||
const [expanded, setExpanded] = useState<Set<number>>(new Set())
|
||||
|
||||
function toggle(submissionNum: number) {
|
||||
setExpanded((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(submissionNum)) next.delete(submissionNum)
|
||||
else next.add(submissionNum)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Fecha</th>
|
||||
<th>Código</th>
|
||||
<th>Pregunta</th>
|
||||
<th>Valor</th>
|
||||
<th className="admin-col-optional">Departamento</th>
|
||||
<th className="admin-col-optional">Sector</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows
|
||||
.filter((row) => row.isFirstInGroup || expanded.has(row.submissionNum))
|
||||
.map((row) => {
|
||||
const groupClass =
|
||||
row.submissionNum % 2 === 0 ? 'admin-group-even' : 'admin-group-odd'
|
||||
const isExpanded = expanded.has(row.submissionNum)
|
||||
const trClass = row.isFirstInGroup
|
||||
? `${groupClass} admin-group-start admin-row-toggle`
|
||||
: groupClass
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={row.key}
|
||||
className={trClass}
|
||||
onClick={row.isFirstInGroup ? () => toggle(row.submissionNum) : undefined}
|
||||
onKeyDown={
|
||||
row.isFirstInGroup
|
||||
? (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
toggle(row.submissionNum)
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
role={row.isFirstInGroup ? 'button' : undefined}
|
||||
tabIndex={row.isFirstInGroup ? 0 : undefined}
|
||||
aria-expanded={row.isFirstInGroup ? isExpanded : undefined}
|
||||
>
|
||||
<td>
|
||||
{row.isFirstInGroup && (
|
||||
<span className="admin-toggle-icon" aria-hidden="true">
|
||||
{isExpanded ? '▼' : '▶'}
|
||||
</span>
|
||||
)}
|
||||
{row.submissionNum}
|
||||
</td>
|
||||
<td>{row.date}</td>
|
||||
<td>{row.code}</td>
|
||||
<td className="admin-prompt">{row.prompt}</td>
|
||||
<td>
|
||||
{row.rendered === null ? (
|
||||
<span className="admin-nodata">sin dato</span>
|
||||
) : (
|
||||
row.rendered
|
||||
)}
|
||||
</td>
|
||||
<td className="admin-col-optional">{row.zone}</td>
|
||||
<td className="admin-col-optional">{row.sector}</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
59
app/api/dashboard/[widget]/route.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { queryToFilters, filtersToRpc } from '@/lib/dashboard-utils'
|
||||
|
||||
// Route Handler server-side: llama a las RPC con el cliente Supabase.
|
||||
// El service_role nunca llega al browser — vive solo en este servidor.
|
||||
// Las RPC son SECURITY DEFINER → aplican k-anonimato y excluyen 3.4/7.1 internamente.
|
||||
|
||||
const RPC_MAP: Record<string, string> = {
|
||||
kpis: 'rpc_dashboard_kpis',
|
||||
prevalencia: 'rpc_prevalencia_por_depto',
|
||||
heatmap: 'rpc_heatmap_intensidad',
|
||||
distribucion: 'rpc_perfil_poblacion',
|
||||
apoyos: 'rpc_apoyos_demandados',
|
||||
score: 'rpc_score_organizacional',
|
||||
piramide: 'rpc_piramide_demografica',
|
||||
intensidad: 'rpc_intensidad_vs_bienestar',
|
||||
saturacion: 'rpc_saturacion_cuidador',
|
||||
}
|
||||
|
||||
function getSupabaseServer() {
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL!
|
||||
// Usar anon key — las RPC son SECURITY DEFINER y aplican sus propias reglas
|
||||
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
return createClient(url, key)
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ widget: string }> }
|
||||
) {
|
||||
const { widget } = await params
|
||||
const rpcName = RPC_MAP[widget]
|
||||
|
||||
if (!rpcName) {
|
||||
return NextResponse.json({ error: 'Widget desconocido' }, { status: 404 })
|
||||
}
|
||||
|
||||
const sp = request.nextUrl.searchParams
|
||||
const surveyId = sp.get('survey_id')
|
||||
if (!surveyId) {
|
||||
return NextResponse.json({ error: 'survey_id requerido' }, { status: 400 })
|
||||
}
|
||||
|
||||
const filters = queryToFilters(sp)
|
||||
const supabase = getSupabaseServer()
|
||||
|
||||
const { data, error } = await supabase.rpc(rpcName, {
|
||||
p_survey_id: surveyId,
|
||||
p_filters: filtersToRpc(filters),
|
||||
})
|
||||
|
||||
if (error) {
|
||||
console.error(`[dashboard/${widget}] RPC error:`, error.message)
|
||||
return NextResponse.json({ error: error.message }, { status: 500 })
|
||||
}
|
||||
|
||||
return NextResponse.json(data)
|
||||
}
|
||||
15
app/dashboard/loading.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export default function DashboardLoading() {
|
||||
return (
|
||||
<div className="db-loading-page" aria-busy="true" aria-label="Cargando dashboard…">
|
||||
<div className="db-skeleton-block" style={{ height: 60, marginBottom: 16 }} />
|
||||
<div className="db-grid">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<div key={i} className="db-skeleton-block" style={{ gridColumn: 'span 3', height: 96 }} />
|
||||
))}
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<div key={i+4} className="db-skeleton-block" style={{ gridColumn: 'span 6', height: 240 }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
100
app/dashboard/page.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { KpiCards } from '@/components/dashboard/kpi-cards'
|
||||
import { ParaguayMap } from '@/components/dashboard/paraguay-map'
|
||||
import { DonutCuidados } from '@/components/dashboard/donut-cuidados'
|
||||
import { GaugeOrganizacional } from '@/components/dashboard/gauge-organizacional'
|
||||
import { HeatmapWidget } from '@/components/dashboard/heatmap-widget'
|
||||
import { PiramideDemografica } from '@/components/dashboard/piramide-demografica'
|
||||
import { ApoyosBar } from '@/components/dashboard/apoyos-bar'
|
||||
import { IntensidadProductividad } from '@/components/dashboard/intensidad-productividad'
|
||||
import { TablaSaturacion } from '@/components/dashboard/tabla-saturacion'
|
||||
import { FilterSidebar } from '@/components/dashboard/filter-sidebar'
|
||||
import type { DashboardFilters } from '@/lib/dashboard-types'
|
||||
|
||||
// El survey_id se pasa como query param: /dashboard?survey_id=UUID
|
||||
// '00000000-0000-0000-0000-000000000000' = flag p_all (vista agregada de todos los surveys)
|
||||
const SURVEY_OPTIONS = [
|
||||
{ label: 'Todos', id: '00000000-0000-0000-0000-000000000000' },
|
||||
{ label: 'InQuality', id: '30000000-0000-0000-0000-000000000002' },
|
||||
{ label: 'Fundación Solidaridad', id: '30000000-0000-0000-0000-000000000003' },
|
||||
{ label: 'Demo', id: '30000000-0000-0000-0000-000000000001' },
|
||||
]
|
||||
|
||||
export default function DashboardPage() {
|
||||
const searchParams = useSearchParams()
|
||||
const surveyId = searchParams.get('survey_id') ?? '30000000-0000-0000-0000-000000000001'
|
||||
const [filters, setFilters] = useState<DashboardFilters>({})
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
|
||||
|
||||
function handleDeptoClick(depto: string) {
|
||||
setFilters(f => ({ ...f, depto: depto || undefined }))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="db-page">
|
||||
<FilterSidebar
|
||||
filters={filters}
|
||||
onChange={setFilters}
|
||||
collapsed={sidebarCollapsed}
|
||||
onToggle={() => setSidebarCollapsed(c => !c)}
|
||||
/>
|
||||
|
||||
<div className="db-main">
|
||||
<header className="db-page-header">
|
||||
<h1 className="db-page-title">Economía del Cuidado — Paraguay</h1>
|
||||
<p className="db-page-subtitle">
|
||||
Datos agregados · k-anonimato k≥5 · Encuesta Mapeo de Cuidadores 2026
|
||||
{filters.depto && <span className="db-active-filter"> · Filtro: {filters.depto}</span>}
|
||||
</p>
|
||||
<nav className="db-survey-nav" aria-label="Selección de empresa">
|
||||
{SURVEY_OPTIONS.map((opt) => (
|
||||
<a
|
||||
key={opt.id}
|
||||
href={`/dashboard?survey_id=${opt.id}`}
|
||||
className={
|
||||
opt.id === surveyId
|
||||
? 'db-survey-link db-survey-link-active'
|
||||
: 'db-survey-link'
|
||||
}
|
||||
aria-current={opt.id === surveyId ? 'page' : undefined}
|
||||
>
|
||||
{opt.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{/* Fila 1: KPIs (span 12) */}
|
||||
<section className="db-section" aria-label="Indicadores clave">
|
||||
<KpiCards surveyId={surveyId} filters={filters} />
|
||||
</section>
|
||||
|
||||
{/* Fila 2: Mapa (6) + Donut (3) + Gauge (3) */}
|
||||
<section className="db-grid db-section" aria-label="Distribución geográfica y tipos">
|
||||
<ParaguayMap surveyId={surveyId} filters={filters} onDeptoClick={handleDeptoClick} />
|
||||
<DonutCuidados surveyId={surveyId} filters={filters} />
|
||||
<GaugeOrganizacional surveyId={surveyId} filters={filters} />
|
||||
</section>
|
||||
|
||||
{/* Fila 3: Heatmap (6) + Pirámide (6) */}
|
||||
<section className="db-grid db-section" aria-label="Intensidad y demografía">
|
||||
<HeatmapWidget surveyId={surveyId} filters={filters} />
|
||||
<PiramideDemografica surveyId={surveyId} filters={filters} />
|
||||
</section>
|
||||
|
||||
{/* Fila 4: Apoyos demandados (6) + Hexbin intensidad/bienestar (6) */}
|
||||
<section className="db-grid db-section" aria-label="Apoyos e impacto">
|
||||
<ApoyosBar surveyId={surveyId} filters={filters} />
|
||||
<IntensidadProductividad surveyId={surveyId} filters={filters} />
|
||||
</section>
|
||||
|
||||
{/* Fila 5: Saturación del cuidador (12) */}
|
||||
<section className="db-grid db-section" aria-label="Saturación del cuidador">
|
||||
<TablaSaturacion surveyId={surveyId} filters={filters} />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
424
app/globals.css
@@ -2,18 +2,27 @@
|
||||
|
||||
/* ── Tokens ── */
|
||||
:root {
|
||||
/* ── Marca RE ── */
|
||||
--color-re-teal: #43BBC8;
|
||||
--color-re-dark: #494F4F;
|
||||
--color-re-orange: #F8AD13;
|
||||
--color-re-orange-intense: #EE761A;
|
||||
--color-re-light: #E7E6E5;
|
||||
--color-re-white: #FFFFFF;
|
||||
|
||||
/* ── Sistema (primario = teal RE) ── */
|
||||
--color-bg: #f9fafb;
|
||||
--color-surface: #ffffff;
|
||||
--color-text: #111827;
|
||||
--color-text-muted: #6b7280;
|
||||
--color-primary: #1d4ed8;
|
||||
--color-primary-hover: #1e40af;
|
||||
--color-primary-light: #dbeafe;
|
||||
--color-accent: #2563eb;
|
||||
--color-primary: #43BBC8;
|
||||
--color-primary-hover: #3DAAB6;
|
||||
--color-primary-light: #e8f8fa;
|
||||
--color-accent: #43BBC8;
|
||||
--color-error: #b91c1c;
|
||||
--color-error-bg: #fef2f2;
|
||||
--color-border: #d1d5db;
|
||||
--color-border-focus: #2563eb;
|
||||
--color-border-focus: #43BBC8;
|
||||
--color-sensitive: #fef3c7;
|
||||
--color-sensitive-border: #f59e0b;
|
||||
--radius: 8px;
|
||||
@@ -23,6 +32,7 @@
|
||||
/* ── Reset & base ── */
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html { font-size: 16px; scroll-behavior: smooth; }
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--color-bg);
|
||||
@@ -30,7 +40,12 @@ body {
|
||||
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
/* El contenido entre header y footer crece para empujar el footer al fondo */
|
||||
.layout-content { flex: 1; }
|
||||
|
||||
/* ── Skip link ── */
|
||||
.skip-link {
|
||||
@@ -132,16 +147,6 @@ body {
|
||||
border-radius: 0 var(--radius) var(--radius) 0;
|
||||
color: var(--color-primary-hover);
|
||||
}
|
||||
.section-sensitive-note {
|
||||
font-size: 0.8125rem;
|
||||
color: #92400e;
|
||||
background: var(--color-sensitive);
|
||||
border: 1px solid var(--color-sensitive-border);
|
||||
border-radius: var(--radius);
|
||||
padding: 8px 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── Question wrapper ── */
|
||||
.question-wrapper {
|
||||
background: var(--color-surface);
|
||||
@@ -168,6 +173,12 @@ body {
|
||||
line-height: 1.4;
|
||||
}
|
||||
.required-mark { color: var(--color-error); }
|
||||
.q-code {
|
||||
font-size: 0.78em;
|
||||
font-weight: 700;
|
||||
color: #7A8FA0;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* ── Options list ── */
|
||||
.options-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
@@ -254,6 +265,26 @@ body {
|
||||
.rating-step:hover .rating-dot { border-color: var(--color-accent); background: var(--color-primary-light); }
|
||||
.rating-step:has(input:checked):hover .rating-dot { background: var(--color-primary-hover); border-color: var(--color-primary-hover); color: #fff; }
|
||||
|
||||
/* ── Text short (single-line input) ── */
|
||||
.text-short-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1.5px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
line-height: 1.5;
|
||||
transition: border-color 0.15s;
|
||||
color: var(--color-text);
|
||||
background: var(--color-surface);
|
||||
}
|
||||
.text-short-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-border-focus);
|
||||
box-shadow: 0 0 0 3px rgba(37,99,235,0.15);
|
||||
}
|
||||
|
||||
/* ── Textarea ── */
|
||||
.text-area {
|
||||
display: block;
|
||||
@@ -378,6 +409,61 @@ body {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* ── Geo select widget ── */
|
||||
.geo-select-wrapper { display: flex; flex-direction: column; gap: 16px; }
|
||||
.geo-select-group { display: flex; flex-direction: column; gap: 8px; }
|
||||
.geo-select {
|
||||
display: block; width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1.5px solid var(--color-border); border-radius: var(--radius);
|
||||
font-size: 1rem; font-family: inherit; background: var(--color-surface);
|
||||
color: var(--color-text); cursor: pointer; appearance: auto;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.geo-select:focus { outline: none; border-color: var(--color-border-focus); box-shadow: 0 0 0 3px rgba(37,99,235,0.15); }
|
||||
.geo-select-disabled { opacity: 0.5; cursor: not-allowed; background: #f3f4f6; }
|
||||
.geo-label-disabled { color: var(--color-text-muted); }
|
||||
.geo-disabled-hint { font-size: 0.8125rem; font-weight: 400; color: var(--color-text-muted); }
|
||||
|
||||
/* ── Brand header ── */
|
||||
.brand-header {
|
||||
background: var(--color-primary-light); border-left: 4px solid var(--color-primary);
|
||||
border-radius: 0 var(--radius) var(--radius) 0;
|
||||
padding: 10px 14px; margin: 16px 0 0;
|
||||
}
|
||||
.brand-text { margin: 0; font-size: 0.875rem; color: var(--color-primary-hover); line-height: 1.5; }
|
||||
|
||||
/* ── Consent screen ── */
|
||||
.consent-container { max-width: 720px; margin: 0 auto; padding: 32px 16px 80px; }
|
||||
.consent-title { font-size: 1.5rem; font-weight: 700; margin: 0 0 24px; }
|
||||
.consent-section { margin-bottom: 24px; }
|
||||
.consent-section-title { font-size: 1.0625rem; font-weight: 700; margin: 0 0 10px; }
|
||||
.consent-list { margin: 8px 0 0; padding-left: 20px; line-height: 1.7; display: flex; flex-direction: column; gap: 8px; }
|
||||
.consent-anon-box {
|
||||
background: var(--color-sensitive); border: 1px solid var(--color-sensitive-border);
|
||||
border-radius: var(--radius); padding: 14px 16px; margin-bottom: 24px;
|
||||
font-size: 0.9375rem; line-height: 1.6;
|
||||
}
|
||||
.consent-checkboxes { margin-bottom: 28px; display: flex; flex-direction: column; gap: 14px; }
|
||||
.consent-checkbox-label {
|
||||
display: flex; align-items: flex-start; gap: 12px;
|
||||
padding: 14px 16px; border: 1.5px solid var(--color-border); border-radius: var(--radius);
|
||||
cursor: pointer; transition: border-color 0.15s, background 0.15s; line-height: 1.5;
|
||||
}
|
||||
.consent-checkbox-label:has(input:checked) { border-color: var(--color-primary); background: var(--color-primary-light); }
|
||||
.consent-checkbox-input { width: 20px; height: 20px; flex-shrink: 0; margin-top: 1px; accent-color: var(--color-primary); cursor: pointer; }
|
||||
.consent-checkbox-text { flex: 1; }
|
||||
.consent-required-badge { display: inline-block; font-size: 0.75rem; font-weight: 600; color: var(--color-primary); background: var(--color-primary-light); padding: 1px 6px; border-radius: 4px; }
|
||||
.consent-optional-badge { display: inline-block; font-size: 0.75rem; font-weight: 600; color: var(--color-text-muted); background: #f3f4f6; padding: 1px 6px; border-radius: 4px; }
|
||||
.consent-footer { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
|
||||
.consent-required-hint { margin: 0; font-size: 0.875rem; color: var(--color-text-muted); }
|
||||
.btn-disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
/* ── Thank you / error screens ── */
|
||||
.thank-you-container, .error-container { max-width: 560px; margin: 80px auto; padding: 0 16px; }
|
||||
.thank-you-title, .error-title { font-size: 1.5rem; font-weight: 700; margin: 0 0 16px; }
|
||||
.thank-you-body { color: var(--color-text-muted); line-height: 1.7; margin: 0 0 12px; }
|
||||
|
||||
/* ── No survey state ── */
|
||||
.no-survey {
|
||||
max-width: 480px;
|
||||
@@ -419,6 +505,314 @@ body {
|
||||
.btn { width: 100%; }
|
||||
}
|
||||
|
||||
/* ── Survey header (marca RE) ── */
|
||||
.survey-header-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 16px 24px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
.survey-header-logo { display: block; height: 48px; width: auto; flex-shrink: 0; }
|
||||
.survey-header-title {
|
||||
color: var(--color-re-white);
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* ── Survey footer ── */
|
||||
.survey-footer-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 24px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
.survey-footer-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.survey-footer-label {
|
||||
color: rgba(255,255,255,0.7);
|
||||
font-size: 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.survey-footer-logo { height: 32px; width: auto; display: block; }
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════
|
||||
DASHBOARD — tema oscuro premium
|
||||
Fondo: #0F1923 · Widgets: #1A2535 · Teal: #43BBC8
|
||||
══════════════════════════════════════════════════════════════ */
|
||||
.db-page {
|
||||
display: flex; min-height: 100vh;
|
||||
background: #0F1923; color: #E7E6E5;
|
||||
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||||
}
|
||||
.db-main { flex: 1; padding: 24px; overflow-x: hidden; }
|
||||
.db-page-header { margin-bottom: 20px; }
|
||||
.db-page-title { font-size: 1.375rem; font-weight: 700; color: #fff; margin: 0 0 4px; }
|
||||
.db-page-subtitle { font-size: 0.8125rem; color: #9ca3af; margin: 0; }
|
||||
.db-active-filter { color: var(--color-re-teal); font-weight: 600; }
|
||||
.db-survey-nav { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
|
||||
.db-survey-link {
|
||||
font-size: 0.75rem; font-weight: 600; padding: 4px 12px; border-radius: 999px;
|
||||
border: 1px solid rgba(67,187,200,0.3); color: #9ca3af; text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.db-survey-link:hover { color: #E7E6E5; border-color: var(--color-re-teal); }
|
||||
.db-survey-link-active { background: var(--color-re-teal); color: #0F1923; border-color: var(--color-re-teal); }
|
||||
.db-section { margin-bottom: 16px; }
|
||||
.db-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* ── Widget shell ── */
|
||||
.db-widget {
|
||||
background: #1A2535;
|
||||
border: 1px solid rgba(67,187,200,0.2);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
.db-widget:hover { box-shadow: 0 0 20px rgba(67,187,200,0.18); }
|
||||
.db-widget-header {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 12px 16px 8px;
|
||||
}
|
||||
.db-widget-title { font-size: 0.875rem; font-weight: 600; color: #E7E6E5; margin: 0; }
|
||||
.db-widget-n { font-size: 0.75rem; color: #9ca3af; }
|
||||
.db-widget-body { padding: 0 16px 16px; }
|
||||
|
||||
/* ── Suppressed / loading states ── */
|
||||
.db-suppressed {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
min-height: 100px; text-align: center; color: #9ca3af; gap: 6px;
|
||||
}
|
||||
.db-suppressed-icon { font-size: 1.5rem; }
|
||||
.db-suppressed-hint { font-size: 0.75rem; color: #6b7280; }
|
||||
.db-suppressed-card {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: 4px; color: #6b7280;
|
||||
}
|
||||
.db-skeleton-block { background: #1e2d42; border-radius: 6px; animation: pulse 1.4s ease-in-out infinite; }
|
||||
.db-error { color: #ef4444; font-size: 0.875rem; padding: 12px; }
|
||||
.db-loading-page { padding: 24px; background: #0F1923; min-height: 100vh; }
|
||||
|
||||
/* ── KPI row ── */
|
||||
.db-kpi-row {
|
||||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
.db-kpi-card {
|
||||
background: #1A2535; border: 1px solid rgba(67,187,200,0.2);
|
||||
border-radius: 10px; padding: 16px; text-align: center;
|
||||
display: flex; flex-direction: column; gap: 4px;
|
||||
transition: box-shadow 0.3s;
|
||||
}
|
||||
.db-kpi-card:hover { box-shadow: 0 0 20px rgba(67,187,200,0.18); }
|
||||
/* Título de KPI: mayúsculas, bold, siempre visible */
|
||||
.db-kpi-title { font-size: 0.8125rem; font-weight: 700; color: #E7E6E5; text-transform: uppercase; letter-spacing: 0.04em; }
|
||||
/* Subtítulo de KPI: código de pregunta + descripción */
|
||||
.db-kpi-subtitle { font-size: 0.6875rem; color: #7A8FA0; font-weight: 400; line-height: 1.4; margin-bottom: 4px; }
|
||||
.db-kpi-value {
|
||||
font-size: 2rem; font-weight: 700; color: var(--color-re-teal);
|
||||
font-variant-numeric: tabular-nums; margin-top: 4px;
|
||||
}
|
||||
.db-kpi-value-suppressed { font-size: 1.25rem; color: #6b7280; }
|
||||
.db-kpi-note { font-size: 0.75rem; color: #9ca3af; }
|
||||
.db-kpi-suppressed-block { display: flex; flex-direction: column; align-items: center; gap: 4px; margin-top: 8px; }
|
||||
/* Compat — se puede quitar si ya no se usa */
|
||||
.db-kpi-label { font-size: 0.8125rem; color: #E7E6E5; }
|
||||
.db-kpi-sub { font-size: 0.75rem; color: #9ca3af; }
|
||||
|
||||
/* Subtítulo de widget-shell */
|
||||
.db-widget-title-group { display: flex; flex-direction: column; gap: 2px; flex: 1; }
|
||||
.db-widget-subtitle { font-size: 0.75rem; color: #7A8FA0; font-weight: 400; margin: 0; line-height: 1.4; }
|
||||
|
||||
/* ── Mapa tile ── */
|
||||
.db-map-wrapper { position: relative; }
|
||||
.db-map-tooltip {
|
||||
position: absolute; top: -28px; left: 50%; transform: translateX(-50%);
|
||||
background: #0F1923; color: #E7E6E5; font-size: 0.75rem;
|
||||
padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(67,187,200,0.3);
|
||||
white-space: nowrap; z-index: 10; pointer-events: none;
|
||||
}
|
||||
.db-tile-grid {
|
||||
display: grid; grid-template-columns: repeat(5, 1fr); gap: 3px;
|
||||
grid-template-rows: repeat(6, 1fr); min-height: 220px;
|
||||
}
|
||||
.db-tile {
|
||||
border: none; border-radius: 4px; cursor: pointer; padding: 4px 2px;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
transition: filter 0.15s, outline 0.15s;
|
||||
min-height: 36px;
|
||||
}
|
||||
.db-tile:hover { filter: brightness(1.2); }
|
||||
.db-tile:focus-visible { outline: 2px solid var(--color-re-teal); outline-offset: 1px; }
|
||||
.db-tile-selected { outline: 2px solid #fff; }
|
||||
.db-tile-suppressed { background-image: repeating-linear-gradient(
|
||||
45deg, transparent, transparent 4px, rgba(255,255,255,0.06) 4px, rgba(255,255,255,0.06) 8px
|
||||
) !important; }
|
||||
.db-tile-name { font-size: 0.5rem; color: #fff; text-align: center; line-height: 1.2; font-weight: 600; }
|
||||
.db-tile-pct { font-size: 0.6rem; color: rgba(255,255,255,0.85); font-weight: 700; }
|
||||
|
||||
/* ── Donut ── */
|
||||
.db-donut-wrapper { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
|
||||
.db-donut-svg { width: 130px; height: 130px; flex-shrink: 0; }
|
||||
.db-donut-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
|
||||
.db-legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.75rem; }
|
||||
.db-legend-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
||||
.db-legend-val { margin-left: auto; font-weight: 600; color: var(--color-re-teal); }
|
||||
|
||||
/* ── Gauge ── */
|
||||
.db-gauge-wrapper { text-align: center; }
|
||||
.db-gauge-svg { width: 100%; max-width: 160px; }
|
||||
.db-gauge-zona { font-size: 0.9rem; font-weight: 700; margin: 4px 0 2px; }
|
||||
.db-gauge-hint { font-size: 0.7rem; color: #6b7280; margin: 0; }
|
||||
|
||||
/* ── Bar chart ── */
|
||||
.db-bar-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.db-bar-item { display: flex; align-items: center; gap: 8px; }
|
||||
.db-bar-label { font-size: 0.75rem; color: #E7E6E5; width: 180px; flex-shrink: 0; line-height: 1.3; }
|
||||
.db-bar-track { flex: 1; background: #2a3547; border-radius: 4px; height: 14px; overflow: hidden; }
|
||||
.db-bar-fill { height: 100%; border-radius: 4px; transition: width 0.5s ease; }
|
||||
.db-bar-pct { font-size: 0.75rem; color: var(--color-re-teal); width: 40px; text-align: right; font-weight: 600; }
|
||||
|
||||
/* ── Heatmap / hexbin shared ── */
|
||||
.db-heatmap-scroll { overflow-x: auto; }
|
||||
.db-heatmap-grid { display: grid; gap: 2px; }
|
||||
.db-heatmap-header {
|
||||
font-size: 0.625rem; color: #9ca3af; text-align: center; padding: 2px;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.db-heatmap-rowlabel { font-size: 0.625rem; color: #9ca3af; padding: 2px 4px; line-height: 1.2; }
|
||||
.db-heatmap-cell {
|
||||
border-radius: 3px; min-height: 32px; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 0.7rem; color: rgba(255,255,255,0.8); transition: filter 0.15s;
|
||||
}
|
||||
.db-heatmap-cell:hover { filter: brightness(1.2); }
|
||||
.db-hexbin-note { font-size: 0.75rem; color: #9ca3af; margin-bottom: 8px; }
|
||||
|
||||
/* ── Pirámide ── */
|
||||
.db-piramide { display: flex; flex-direction: column; gap: 6px; }
|
||||
.db-piramide-legend { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
|
||||
.db-piramide-row { display: flex; align-items: center; gap: 6px; }
|
||||
.db-piramide-label { font-size: 0.7rem; color: #9ca3af; width: 90px; flex-shrink: 0; text-align: right; }
|
||||
.db-piramide-bars { flex: 1; display: flex; gap: 2px; }
|
||||
.db-piramide-bar-wrap { display: flex; flex-direction: column; }
|
||||
.db-piramide-bar { height: 20px; border-radius: 3px; min-width: 4px; transition: width 0.4s; }
|
||||
.db-piramide-n { font-size: 0.7rem; color: #9ca3af; width: 24px; text-align: right; }
|
||||
|
||||
/* ── Tabla riesgo ── */
|
||||
.db-table-wrapper { overflow-x: auto; }
|
||||
.db-table-note { font-size: 0.75rem; color: #9ca3af; margin-bottom: 10px; }
|
||||
.db-table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }
|
||||
.db-table th { color: #9ca3af; font-weight: 600; text-align: left; padding: 8px 12px; border-bottom: 1px solid #2a3547; }
|
||||
.db-table td { padding: 10px 12px; border-bottom: 1px solid #1e2d42; color: #E7E6E5; }
|
||||
.db-table tr:last-child td { border-bottom: none; }
|
||||
.db-risk-badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 0.75rem; font-weight: 700; color: #fff; }
|
||||
|
||||
/* ── Sidebar ── */
|
||||
.db-sidebar {
|
||||
width: 220px; flex-shrink: 0; background: #14202e;
|
||||
border-right: 1px solid rgba(67,187,200,0.15);
|
||||
padding: 12px; transition: width 0.3s ease;
|
||||
display: flex; flex-direction: column; gap: 8px;
|
||||
}
|
||||
.db-sidebar-collapsed { width: 52px; }
|
||||
.db-sidebar-toggle {
|
||||
background: none; border: 1px solid rgba(67,187,200,0.3);
|
||||
color: var(--color-re-teal); border-radius: 6px; padding: 6px;
|
||||
cursor: pointer; font-size: 0.75rem; white-space: nowrap;
|
||||
}
|
||||
.db-sidebar-content { display: flex; flex-direction: column; gap: 16px; overflow-y: auto; }
|
||||
.db-filter-group { display: flex; flex-direction: column; gap: 4px; }
|
||||
.db-filter-label { font-size: 0.75rem; font-weight: 600; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.db-filter-select {
|
||||
background: #1e2d42; border: 1px solid #2a3547; color: #E7E6E5;
|
||||
border-radius: 6px; padding: 6px 8px; font-size: 0.8125rem; width: 100%;
|
||||
}
|
||||
.db-filter-check { display: flex; align-items: center; gap: 6px; font-size: 0.75rem; color: #E7E6E5; cursor: pointer; }
|
||||
.db-filter-check input { accent-color: var(--color-re-teal); width: 14px; height: 14px; }
|
||||
.db-filter-reset {
|
||||
background: rgba(67,187,200,0.1); border: 1px solid rgba(67,187,200,0.3);
|
||||
color: var(--color-re-teal); border-radius: 6px; padding: 8px;
|
||||
cursor: pointer; font-size: 0.8125rem; font-weight: 600;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.db-filter-reset:hover { background: rgba(67,187,200,0.2); }
|
||||
|
||||
/* ── Admin: control de respuestas (Etapa 3V) ── */
|
||||
.admin-page { max-width: 1100px; margin: 0 auto; padding: 24px; }
|
||||
.admin-header {
|
||||
display: flex; align-items: flex-start; justify-content: space-between;
|
||||
gap: 16px; margin-bottom: 16px; flex-wrap: wrap;
|
||||
}
|
||||
.admin-title { font-size: 1.375rem; font-weight: 700; color: var(--color-text); margin: 0 0 4px; }
|
||||
.admin-subtitle { font-size: 0.8125rem; color: var(--color-text-muted); margin: 0; }
|
||||
.admin-survey-nav { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
|
||||
.admin-survey-link {
|
||||
font-size: 0.75rem; font-weight: 600; padding: 4px 12px; border-radius: 999px;
|
||||
border: 1px solid var(--color-border); color: var(--color-text-muted); text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.admin-survey-link:hover { color: var(--color-text); border-color: var(--color-primary); }
|
||||
.admin-survey-link-active { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }
|
||||
.admin-refresh {
|
||||
display: inline-block; padding: 8px 16px; border-radius: var(--radius);
|
||||
background: var(--color-primary); color: #fff; font-size: 0.8125rem;
|
||||
font-weight: 600; text-decoration: none; white-space: nowrap;
|
||||
}
|
||||
.admin-refresh:hover { background: var(--color-primary-hover); }
|
||||
.admin-table-wrapper {
|
||||
overflow-x: auto; background: var(--color-surface); border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius); box-shadow: var(--shadow-card);
|
||||
}
|
||||
.admin-table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }
|
||||
.admin-table th {
|
||||
position: sticky; top: 0; background: var(--color-re-dark); color: #fff;
|
||||
font-weight: 600; text-align: left; padding: 10px 12px; white-space: nowrap;
|
||||
}
|
||||
.admin-table td { padding: 8px 12px; border-bottom: 1px solid var(--color-border); color: var(--color-text); vertical-align: top; }
|
||||
.admin-table tbody tr:last-child td { border-bottom: none; }
|
||||
.admin-table tbody tr.admin-group-even { background: var(--color-bg); }
|
||||
.admin-table tbody tr.admin-group-start td { border-top: 2px solid var(--color-re-dark); }
|
||||
.admin-table tbody tr:hover { background: var(--color-primary-light); }
|
||||
.admin-prompt { color: var(--color-text); }
|
||||
.admin-row-toggle { cursor: pointer; }
|
||||
.admin-toggle-icon {
|
||||
display: inline-block; width: 14px; margin-right: 4px;
|
||||
color: var(--color-text-muted); font-size: 0.7rem;
|
||||
}
|
||||
.admin-nodata {
|
||||
display: inline-block; padding: 1px 8px; border-radius: 10px;
|
||||
background: var(--color-re-light); color: var(--color-re-dark);
|
||||
opacity: 0.6; font-size: 0.7rem; font-style: italic;
|
||||
}
|
||||
.admin-empty { padding: 24px; text-align: center; color: var(--color-text-muted); }
|
||||
.admin-error { color: var(--color-error); font-weight: 600; }
|
||||
.admin-error-detail {
|
||||
background: var(--color-error-bg); border-radius: var(--radius);
|
||||
padding: 12px; font-family: 'Courier New', monospace; font-size: 0.75rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 768px) {
|
||||
.db-kpi-row { grid-template-columns: repeat(2,1fr); }
|
||||
.db-sidebar { width: 100%; border-right: none; border-bottom: 1px solid rgba(67,187,200,0.15); }
|
||||
.db-page { flex-direction: column; }
|
||||
.admin-col-optional { display: none; }
|
||||
}
|
||||
|
||||
/* ── Reduced motion ── */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Metadata, Viewport } from 'next'
|
||||
import './globals.css'
|
||||
import { SurveyHeader } from '@/components/survey-header'
|
||||
import { SurveyFooter } from '@/components/survey-footer'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Mapeo de Cuidadores',
|
||||
@@ -17,7 +19,11 @@ export const viewport: Viewport = {
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="es">
|
||||
<body>{children}</body>
|
||||
<body>
|
||||
<SurveyHeader />
|
||||
<div className="layout-content">{children}</div>
|
||||
<SurveyFooter />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
50
app/page.tsx
@@ -1,21 +1,27 @@
|
||||
import { createClient } from '@/lib/supabase'
|
||||
import type { Question, Survey } from '@/lib/types'
|
||||
import type { BrandConfig, Question, Survey } from '@/lib/types'
|
||||
import { SurveyForm } from '@/components/survey-form'
|
||||
|
||||
// Force dynamic rendering — page fetches from Supabase at request time.
|
||||
// Never statically generated: credentials live in .env.local (runtime only).
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
// Server Component — fetches survey with anon key; RLS ensures only status='live' is returned.
|
||||
export default async function Page() {
|
||||
// In Next.js 15, searchParams is a Promise and must be awaited.
|
||||
interface PageProps {
|
||||
searchParams: Promise<{ s?: string }>
|
||||
}
|
||||
|
||||
export default async function Page({ searchParams }: PageProps) {
|
||||
const { s: surveyId } = await searchParams
|
||||
const supabase = createClient()
|
||||
|
||||
// 1. Fetch the live survey
|
||||
const { data: surveyRow, error: surveyError } = await supabase
|
||||
// 1. Fetch survey — by ?s=UUID if provided, otherwise first live survey
|
||||
const surveyQuery = supabase
|
||||
.from('surveys')
|
||||
.select('id, title, status, is_anonymous, k_threshold')
|
||||
.eq('status', 'live')
|
||||
.maybeSingle()
|
||||
.select('id, org_id, title, status, is_anonymous, k_threshold, consent_version_id, brand_config')
|
||||
|
||||
const { data: surveyRow, error: surveyError } = surveyId
|
||||
? await surveyQuery.eq('id', surveyId).maybeSingle()
|
||||
: await surveyQuery.eq('status', 'live').maybeSingle()
|
||||
|
||||
if (surveyError) {
|
||||
return (
|
||||
@@ -32,10 +38,11 @@ export default async function Page() {
|
||||
<div className="no-survey" role="main">
|
||||
<h1 className="no-survey-title">No hay encuesta activa</h1>
|
||||
<p>
|
||||
Para ver el formulario, la encuesta debe estar en estado <code>live</code>.
|
||||
Para ver el formulario, la encuesta debe estar en estado <code>live</code>,
|
||||
o pasá el UUID por parámetro: <code>?s=UUID</code>.
|
||||
</p>
|
||||
<pre className="no-survey-hint">
|
||||
{`-- Activar la encuesta en Supabase Cloud:\nUPDATE surveys\nSET status = 'live'\nWHERE id = '30000000-0000-0000-0000-000000000001';`}
|
||||
{`-- Activar en Supabase Cloud:\nUPDATE surveys SET status = 'live'\nWHERE id = '30000000-0000-0000-0000-000000000001';`}
|
||||
</pre>
|
||||
</div>
|
||||
)
|
||||
@@ -61,20 +68,19 @@ export default async function Page() {
|
||||
|
||||
const questions = questionRows as Question[]
|
||||
|
||||
// 3. Log question count for validation (visible in server logs and browser Network tab)
|
||||
console.log(
|
||||
`[2A] Survey loaded: id=${surveyRow.id} | questions=${questions.length}`
|
||||
)
|
||||
console.log(`[2B] Survey loaded: id=${surveyRow.id} | questions=${questions.length}`)
|
||||
|
||||
const survey: Survey = {
|
||||
...(surveyRow as Omit<Survey, 'questions'>),
|
||||
id: surveyRow.id,
|
||||
org_id: surveyRow.org_id,
|
||||
title: surveyRow.title,
|
||||
status: surveyRow.status,
|
||||
is_anonymous: surveyRow.is_anonymous,
|
||||
k_threshold: surveyRow.k_threshold,
|
||||
consent_version_id: surveyRow.consent_version_id,
|
||||
brand_config: (surveyRow.brand_config as BrandConfig) ?? null,
|
||||
questions,
|
||||
}
|
||||
|
||||
return (
|
||||
<SurveyForm
|
||||
survey={survey}
|
||||
questions={questions}
|
||||
/>
|
||||
)
|
||||
return <SurveyForm survey={survey} questions={questions} />
|
||||
}
|
||||
|
||||
BIN
assets/.DS_Store
vendored
Normal file
BIN
assets/fundacion/.DS_Store
vendored
Normal file
BIN
assets/fundacion/ISOLOGO HORIZONATAL-FS.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
assets/fundacion/ISOLOGO VERTICAL TAGLINE-FS-28.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/fundacion/ISOLOGO VERTICAL-FS.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
assets/fundacion/ISOTIPO_FS.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/fundacion/LOGOTIPO-FS.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/fundacion/Versión Horiz_Isologotipo FS-17.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
380
assets/fundacion/Versión Horiz_Isologotipo FS-17.svg
Normal file
@@ -0,0 +1,380 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="535.614px" height="308.44px" viewBox="0 0 535.614 308.44" enable-background="new 0 0 535.614 308.44"
|
||||
xml:space="preserve">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#129539" d="M181.184,99.452c-0.34-0.962-0.875-1.782-1.596-2.409
|
||||
c-0.729-0.643-1.628-1.059-2.608-1.183c-2.093-0.275-4.58,0.708-6.829,3.731c-1.76,1.805-4.482,5.509-7.164,9.176
|
||||
c-2.851,3.915-5.667,7.738-7.12,9.029c-0.727,0.664-1.556,0.967-2.206,0.945c-0.281-0.027-0.548-0.098-0.732-0.238h-0.021
|
||||
c-0.165-0.14-0.303-0.323-0.4-0.588c-0.167-0.584-0.046-1.432,0.492-2.549c2.257-4.683,6.057-12.303,9.529-19.074
|
||||
c2.979-5.827,5.676-11.001,6.939-13.166c2.279-3.803,2.374-7.205,1.248-9.457c-0.494-0.939-1.199-1.711-2.065-2.182
|
||||
c-0.843-0.491-1.855-0.723-2.933-0.604c-2.441,0.232-5.228,2.177-7.482,6.47c-2.673,5.163-5.185,9.78-7.674,14.381
|
||||
c-2.981,5.466-5.913,10.86-8.935,16.779c-0.521,0.815-1.193,1.426-1.828,1.701c-0.289,0.124-0.524,0.194-0.737,0.151
|
||||
c-0.162-0.027-0.319-0.124-0.44-0.308c-0.259-0.324-0.421-0.913-0.421-1.831c-0.027-3.052,3.124-12.577,5.627-20.154
|
||||
c1.037-3.191,1.971-6.021,2.536-7.933c0.891-3.116,2.374-8.484,3.521-12.945c1.032-3.937,1.828-7.177,1.828-7.625
|
||||
c-0.021-2.23-0.516-3.916-1.291-5.114c-0.748-1.129-1.709-1.81-2.819-2.128c-1.075-0.34-2.252-0.286-3.391,0.043
|
||||
c-1.512,0.47-2.962,1.432-4.094,2.787c-1.755,2.139-4.102,10.217-6.045,16.968l-0.608,2.058c-0.542,1.879-1.574,5.59-2.724,9.764
|
||||
c-2.09,7.646-4.623,16.871-5.422,19.031c-0.373,1.053-1.01,1.798-1.685,2.149c-0.275,0.146-0.591,0.216-0.851,0.194
|
||||
c-0.232-0.027-0.438-0.124-0.632-0.308c-0.395-0.378-0.702-1.097-0.748-2.252c-0.327-5.654,0.256-13.118,0.845-20.506
|
||||
c0.491-6.194,0.958-12.313,0.915-17.319c0.235-3.32-0.842-5.643-2.468-6.934c-0.775-0.632-1.685-1.01-2.646-1.156
|
||||
c-0.942-0.135-1.928-0.021-2.86,0.324c-2.068,0.756-3.896,2.685-4.601,5.676c-1.269,5.455-1.925,13.686-2.533,21.311
|
||||
c-0.588,7.463-1.15,14.332-2.16,17.222c-0.448,1.215-1.199,3.24-2.136,4.854c-0.821,1.383-1.736,2.506-2.536,2.35
|
||||
c-2.533-0.48-2.859-2.587-3.14-4.396c-0.046-0.389-0.119-0.793-0.189-1.16c-0.235-1.912-0.603-3.904-1.078-5.801
|
||||
c-0.448-1.922-1.007-3.774-1.593-5.4c-1.064-2.835-3.005-5.551-5.239-7.344c-1.08-0.838-2.249-1.496-3.416-1.831
|
||||
c-1.226-0.318-2.468-0.318-3.664,0.118c-1.013,0.373-1.974,1.086-2.841,2.166c-2.136,2.674-0.632,6.658,1.288,11.849
|
||||
c1.95,5.228,4.393,11.751,3.832,19.042c-0.127,1.522-0.24,3.057-0.383,4.53c-0.762,9.202-1.38,17.33-0.148,26.965
|
||||
c1.045,0.34,2.092,0.681,3.181,1.074c0,0-0.256-0.967,1.248-11.789c2.787-20.073,27.423-29.054,45.787-18.923
|
||||
c12.418,6.854,17.333,18.696,18.124,31.619c3.98-6.604,6.618-12.285,7.59-15.429c0.443-1.501,2.959-5.396,6-9.808
|
||||
c4.577-6.62,10.398-14.321,12.461-16.8C181.532,104.62,181.951,101.683,181.184,99.452z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#163F75" d="M153.942,161.811c-0.454,0-0.856,0-1.302,0
|
||||
c-0.934,8.527-3.151,17-5.765,24.361c-8.868,25.03-8.111,45.039-45.396,37.861c-11.679-2.246-24.604-4.73-36.032-8.883
|
||||
c14.938,42.295,79.458,45.109,90.397,6.248C159.669,207.789,161.961,181.332,153.942,161.811z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#E00914" d="M91.254,160.644c3.381,2.273-0.47-4.86,2.627-17.275
|
||||
c4.412-17.567,20.135-29.179,36.528-20.128c21.699,11.968,17.783,40.968,10.744,60.857c-7.925,22.357-7.272,40.238-40.563,33.833
|
||||
c-26.197-5.065-59.521-11.405-60.733-39.601C39.311,165.963,57.051,133.426,91.254,160.644L91.254,160.644z"/>
|
||||
<path fill="#E00914" d="M199.191,221.652c4.323,0,7.555-1.291,9.834-3.916c1.733-1.943,2.6-4.504,2.6-7.604
|
||||
c0-3.37-1.288-6.658-3.823-9.872c-1.167-1.458-2.487-2.884-3.848-4.271c-3.378-3.327-5.77-6.118-7.131-8.489
|
||||
c-1.547-2.582-2.301-5.298-2.301-8.182c0-3.041,0.548-5.471,1.577-7.302c1.245-2.208,3.07-3.326,5.422-3.326
|
||||
c3.256,0,5.365,1.998,6.421,5.908c0.467,1.755,0.678,3.71,0.678,5.789c0,2.819-0.278,5.54-0.842,8.235
|
||||
c-0.07,0.384-0.116,0.751-0.116,1.129c0,1.388,0.681,2.09,2.063,2.09c1.528,0,2.605-1.388,3.243-4.175
|
||||
c0.488-2.024,0.721-4.46,0.721-7.279c0-11.276-4.404-16.888-13.25-16.888c-4.226,0-7.625,1.167-10.158,3.56
|
||||
c-1.312,1.22-2.344,2.689-3.075,4.438c-1.007,2.463-1.526,5.282-1.526,8.377c0,4.525,1.196,8.678,3.621,12.383
|
||||
c1.658,2.581,4.072,5.465,7.345,8.635c1.142,1.178,2.228,2.279,3.227,3.381c2.182,2.42,3.238,4.672,3.238,6.756
|
||||
c0,3.262-1.48,4.903-4.429,4.903c-2.651,0-4.366-1.388-5.076-4.196c-0.33-1.145-0.486-2.327-0.486-3.45
|
||||
c0-1.858-0.751-2.792-2.252-2.792c-1.645,0-2.671,1.571-2.671,3.64c0,3.656,0.772,6.54,2.387,8.646
|
||||
C192.557,220.361,195.419,221.652,199.191,221.652L199.191,221.652z M234.038,216.235c-3.008,0-5.238-1.755-6.71-5.249
|
||||
c-1.296-3.121-1.947-7.345-1.947-12.691c0-10.963,3.138-16.428,9.402-16.428c2.889,0,4.95,1.782,6.267,5.373
|
||||
c1.013,2.781,1.499,6.243,1.499,10.461c0,5.093-0.635,9.321-1.877,12.669C239.192,214.291,236.989,216.235,234.038,216.235
|
||||
L234.038,216.235z M231.667,221.587c5.819,0,10.134-2.278,12.853-6.848c2.39-4.062,3.591-9.612,3.591-16.703
|
||||
c0-5.563-0.872-10.164-2.632-13.749c-2.417-4.926-6.189-7.394-11.395-7.394c-6.011,0-10.487,2.273-13.425,6.874
|
||||
c-2.603,3.991-3.918,9.451-3.918,16.348c0,5.897,1.034,10.66,3.124,14.365C222.519,219.238,226.48,221.587,231.667,221.587
|
||||
L231.667,221.587z M263.008,221.728c0.889,0,1.974-0.141,3.211-0.491c1.218-0.33,2.233-0.768,2.983-1.41
|
||||
c0.554-0.47,0.861-0.988,0.861-1.62c0-0.794-0.486-1.198-1.499-1.198c-0.324,0-0.821,0.075-1.477,0.265
|
||||
c-0.426,0.118-0.918,0.184-1.474,0.184c-1.855,0-2.187-1.523-2.187-9.219v-38.548c0-1.62-0.284-2.9-0.77-3.915
|
||||
c-0.732-1.523-2.001-2.274-3.751-2.274c-3.103,0-4.137,1.707-4.137,6.497v33.126c0,2.933,0.076,5.697,0.262,8.214
|
||||
c0.21,2.717,0.772,4.925,1.663,6.681C257.958,220.485,260.049,221.728,263.008,221.728L263.008,221.728z M279.279,174.36
|
||||
c1.653,0,2.825-0.61,3.594-1.755c0.545-0.777,0.805-1.717,0.805-2.797c0-1.193-0.4-2.317-1.205-3.327
|
||||
c-0.767-1.01-1.831-1.534-3.122-1.534c-2.53,0-4.331,2.215-4.331,5.331C275.021,173,276.447,174.36,279.279,174.36L279.279,174.36z
|
||||
M279.123,221.652c3.019,0,4.556-2.08,4.556-6.238v-32.023c0-4.828-1.129-6.497-4.129-6.497c-2.983,0-4.528,2.16-4.528,6.199v32.02
|
||||
c0,1.102,0.089,2.133,0.232,3.143C275.583,220.556,276.849,221.652,279.123,221.652L279.123,221.652z M309.905,193.721
|
||||
c0,2.9-0.237,5.936-0.683,9.073c-0.421,3.1-1.08,5.746-1.947,7.884c-1.571,3.965-3.542,5.936-5.935,5.936
|
||||
c-3.029,0.162-3.988-6.729-3.988-14.009c0-4.963,1.482-20.43,8.587-20.43c1.949,0,3.138,0.47,3.634,1.447
|
||||
c0.211,0.476,0.332,1.037,0.332,1.642V193.721z M318.535,169.997c0-3.208-0.138-6.497-4.131-6.497c-3.467,0-4.499,3.009-4.499,6.189
|
||||
v7.458c0,0.572-0.078,0.875-0.259,0.875c-0.189,0-0.5-0.119-0.867-0.303c-1.083-0.572-2.279-0.826-3.521-0.826
|
||||
c-4.698,0-8.352,2.673-11.006,8.073c-2.481,5.109-3.729,11.806-3.729,20.035c0,8.83,3.238,16.796,9.602,16.65
|
||||
c3.375,0,6.186-1.756,8.435-5.229c0.662-1.068,1.04-1.642,1.153-1.642s0.191,0.232,0.191,0.632c0,3.191,1.032,6.238,4.499,6.238
|
||||
c3.994,0,4.131-3.278,4.131-6.54V169.997z M348.777,221.652c3.964,0.075,4.061-3.322,4.129-6.54v-31.722
|
||||
c-0.068-3.139-0.165-6.563-4.129-6.497c-1.385,0-2.443,0.427-3.075,1.226c-0.208,0.281-0.462,0.416-0.815,0.416
|
||||
c-0.241,0-0.6-0.107-0.97-0.329c-1.501-0.864-2.951-1.313-4.355-1.313c-4.72,0-8.387,2.673-11.03,8.073
|
||||
c-2.484,5.109-3.729,11.806-3.729,20.035c0,8.83,3.265,16.796,9.597,16.65c3.794,0,6.826-2.08,9.121-6.26
|
||||
c0.259-0.496,0.448-0.729,0.567-0.729c0.116,0,0.167,0.275,0.167,0.75C344.254,218.605,345.299,221.652,348.777,221.652
|
||||
L348.777,221.652z M335.616,216.613c-3.027,0.162-3.991-6.729-3.991-14.009c0-4.963,1.477-20.43,8.586-20.43
|
||||
c2.7,0,4.042,0.967,4.042,2.911v10.342c0,1.879-0.216,4.336-0.664,7.366c-0.467,3.029-1.128,5.681-2.042,7.884
|
||||
C339.999,214.643,338.006,216.613,335.616,216.613L335.616,216.613z M365.996,221.652c3.027,0,4.525-2.08,4.525-6.238v-9.996
|
||||
c0-6.988,0.889-12.664,2.649-17c1.763-4.391,3.78-6.551,5.986-6.551c0.651,0,1.212,0.211,1.712,0.653
|
||||
c0.632,0.572,1.24,0.87,1.828,0.87c1.62,0,2.887-1.664,2.887-3.44c0-1.977-2.322-3.057-5.16-3.057c-3.705,0-6.731,2.068-9.081,6.199
|
||||
c-0.259,0.486-0.489,0.751-0.629,0.751c-0.122,0-0.192-0.265-0.192-0.805c0-4.104-1.499-6.146-4.525-6.146
|
||||
c-3.894,0-4.128,3.262-4.128,6.448v31.771c0,1.069,0.097,2.133,0.235,3.143C362.429,220.556,363.739,221.652,365.996,221.652
|
||||
L365.996,221.652z M393.254,174.36c1.645,0,2.841-0.61,3.616-1.755c0.516-0.777,0.799-1.717,0.799-2.797
|
||||
c0-1.193-0.427-2.317-1.207-3.327c-0.789-1.01-1.823-1.534-3.138-1.534c-2.533,0-4.315,2.215-4.315,5.331
|
||||
C389.01,173,390.414,174.36,393.254,174.36L393.254,174.36z M393.12,221.652c2.997,0,4.55-2.08,4.55-6.238v-32.023
|
||||
c0-4.828-1.134-6.497-4.137-6.497c-2.997,0-4.523,2.16-4.523,6.199v32.02c0,1.102,0.067,2.133,0.229,3.143
|
||||
C389.571,220.556,390.838,221.652,393.12,221.652L393.12,221.652z M423.867,193.721c0,2.9-0.205,5.936-0.656,9.073
|
||||
c-0.418,3.1-1.077,5.746-1.95,7.884c-1.566,3.965-3.534,5.936-5.951,5.936c-3.029,0.162-3.999-6.729-3.999-14.009
|
||||
c0-4.963,1.51-20.43,8.619-20.43c1.917,0,3.122,0.47,3.613,1.447c0.235,0.476,0.324,1.037,0.324,1.642V193.721z M432.529,169.997
|
||||
c0-3.208-0.167-6.497-4.128-6.497c-3.473,0-4.534,3.009-4.534,6.189v7.458c0,0.572-0.043,0.875-0.254,0.875
|
||||
c-0.184,0-0.467-0.119-0.867-0.303c-1.083-0.572-2.255-0.826-3.494-0.826c-4.72,0-8.378,2.673-11.003,8.073
|
||||
c-2.511,5.109-3.731,11.806-3.731,20.035c0,8.83,3.237,16.796,9.575,16.65c3.4,0,6.187-1.756,8.444-5.229
|
||||
c0.675-1.068,1.053-1.642,1.169-1.642c0.119,0,0.162,0.232,0.162,0.632c0,3.191,1.061,6.238,4.534,6.238
|
||||
c3.961,0,4.128-3.278,4.128-6.54V169.997z M462.744,221.652c3.985,0.075,4.058-3.322,4.15-6.54v-31.722
|
||||
c-0.092-3.139-0.165-6.563-4.15-6.497c-1.364,0-2.417,0.427-3.049,1.226c-0.21,0.281-0.494,0.416-0.823,0.416
|
||||
c-0.257,0-0.607-0.107-0.961-0.329c-1.501-0.864-2.957-1.313-4.391-1.313c-4.714,0-8.373,2.673-10.995,8.073
|
||||
c-2.492,5.109-3.737,11.806-3.737,20.035c0,8.83,3.24,16.796,9.578,16.65c3.82,0,6.848-2.08,9.121-6.26
|
||||
c0.259-0.496,0.454-0.729,0.567-0.729c0.119,0,0.178,0.275,0.178,0.75C458.232,218.605,459.271,221.652,462.744,221.652
|
||||
L462.744,221.652z M449.602,216.613c-3.024,0.162-4.001-6.729-4.001-14.009c0-4.963,1.499-20.43,8.605-20.43
|
||||
c2.697,0,4.026,0.967,4.026,2.911v10.342c0,1.879-0.203,4.336-0.648,7.366c-0.467,3.029-1.131,5.681-2.044,7.884
|
||||
C453.974,214.643,452,216.613,449.602,216.613L449.602,216.613z M492.913,193.721c0,2.9-0.208,5.936-0.656,9.073
|
||||
c-0.427,3.1-1.08,5.746-1.95,7.884c-1.563,3.965-3.54,5.936-5.935,5.936c-3.027,0.162-4.013-6.729-4.013-14.009
|
||||
c0-4.963,1.507-20.43,8.614-20.43c1.944,0,3.143,0.47,3.616,1.447c0.232,0.476,0.324,1.037,0.324,1.642V193.721z M501.573,169.997
|
||||
c0-3.208-0.148-6.497-4.137-6.497c-3.461,0-4.523,3.009-4.523,6.189v7.458c0,0.572-0.048,0.875-0.237,0.875
|
||||
c-0.203,0-0.492-0.119-0.867-0.303c-1.102-0.572-2.274-0.826-3.519-0.826c-4.717,0-8.373,2.673-11.003,8.073
|
||||
c-2.484,5.109-3.729,11.806-3.729,20.035c0,8.83,3.24,16.796,9.57,16.65c3.405,0,6.224-1.756,8.449-5.229
|
||||
c0.681-1.068,1.053-1.642,1.169-1.642c0.119,0,0.167,0.232,0.167,0.632c0,3.191,1.062,6.238,4.523,6.238
|
||||
c3.988,0,4.137-3.278,4.137-6.54V169.997z"/>
|
||||
<path fill="#2A2A2B" d="M190.7,230.309v11.546h-1.399v-11.546H190.7z M194.639,236.648v5.206h-1.334v-8.651h1.288v1.361h0.122
|
||||
c0.213-0.454,0.516-0.805,0.913-1.086c0.424-0.265,0.964-0.395,1.62-0.395c0.864,0,1.574,0.275,2.111,0.816
|
||||
c0.54,0.518,0.821,1.365,0.821,2.462v5.492h-1.336v-5.4c0-0.702-0.184-1.215-0.54-1.587c-0.348-0.4-0.824-0.589-1.458-0.589
|
||||
c-0.632,0-1.145,0.21-1.569,0.637C194.853,235.314,194.639,235.903,194.639,236.648L194.639,236.648z M206.377,233.203v1.123h-1.855
|
||||
v5.045c0,0.399,0.07,0.652,0.165,0.842c0.122,0.194,0.256,0.335,0.427,0.378c0.181,0.07,0.367,0.098,0.556,0.098
|
||||
c0.138,0,0.281,0,0.375-0.027c0.092-0.026,0.162-0.026,0.213-0.043l0.281,1.188c-0.097,0.048-0.211,0.069-0.381,0.118
|
||||
c-0.159,0.021-0.373,0.043-0.629,0.043c-0.378,0-0.721-0.064-1.099-0.237c-0.356-0.156-0.656-0.41-0.894-0.751
|
||||
c-0.24-0.318-0.351-0.739-0.351-1.231v-5.422h-1.293v-1.123h1.293v-2.073h1.337v2.073H206.377z M211.915,242.044
|
||||
c-0.826,0-1.55-0.189-2.144-0.557c-0.607-0.389-1.075-0.896-1.41-1.556c-0.324-0.675-0.486-1.457-0.486-2.344
|
||||
c0-0.863,0.162-1.663,0.486-2.349c0.335-0.675,0.781-1.199,1.364-1.566c0.594-0.41,1.267-0.589,2.071-0.589
|
||||
c0.442,0,0.885,0.07,1.336,0.232c0.418,0.141,0.845,0.4,1.199,0.724c0.346,0.33,0.653,0.772,0.861,1.34
|
||||
c0.208,0.545,0.308,1.22,0.308,2.041v0.562h-6.284c0.046,0.918,0.303,1.62,0.789,2.111c0.494,0.497,1.134,0.751,1.909,0.751
|
||||
c0.535,0,0.978-0.113,1.361-0.324c0.37-0.237,0.651-0.562,0.815-1.036l1.293,0.372c-0.216,0.664-0.61,1.172-1.218,1.583
|
||||
C213.551,241.833,212.803,242.044,211.915,242.044L211.915,242.044z M209.217,236.838h4.95c0-0.734-0.219-1.334-0.665-1.831
|
||||
c-0.416-0.47-0.982-0.729-1.706-0.729c-0.524,0-0.967,0.113-1.315,0.351c-0.375,0.238-0.688,0.567-0.894,0.939
|
||||
C209.377,235.969,209.263,236.395,209.217,236.838L209.217,236.838z M217.54,241.854v-8.651h1.291v1.307h0.076
|
||||
c0.154-0.416,0.435-0.767,0.861-1.053c0.427-0.254,0.87-0.395,1.41-0.395c0.089,0,0.208,0,0.351,0
|
||||
c0.143,0.021,0.262,0.021,0.334,0.021v1.367c-0.021-0.033-0.143-0.033-0.311-0.06c-0.146-0.021-0.327-0.044-0.494-0.044
|
||||
c-0.632,0-1.145,0.189-1.569,0.589c-0.429,0.378-0.604,0.87-0.604,1.458v5.46H217.54z M230.797,233.203l-3.189,8.651h-1.363
|
||||
l-3.219-8.651h1.455l2.393,6.891h0.097l2.395-6.891H230.797z M235.857,242.044c-0.807,0-1.539-0.189-2.155-0.557
|
||||
c-0.583-0.389-1.056-0.896-1.382-1.556c-0.33-0.675-0.491-1.457-0.491-2.344c0-0.863,0.162-1.663,0.491-2.349
|
||||
c0.327-0.675,0.772-1.199,1.361-1.566c0.589-0.41,1.269-0.589,2.068-0.589c0.443,0,0.886,0.07,1.334,0.232
|
||||
c0.416,0.141,0.848,0.4,1.193,0.724c0.354,0.33,0.665,0.772,0.87,1.34c0.21,0.545,0.305,1.22,0.305,2.041v0.562h-6.286
|
||||
c0.046,0.918,0.302,1.62,0.796,2.111c0.492,0.497,1.129,0.751,1.896,0.751c0.545,0,0.988-0.113,1.364-0.324
|
||||
c0.375-0.237,0.659-0.562,0.826-1.036l1.285,0.372c-0.216,0.664-0.607,1.172-1.215,1.583
|
||||
C237.5,241.833,236.751,242.044,235.857,242.044L235.857,242.044z M233.166,236.838h4.952c0-0.734-0.21-1.334-0.664-1.831
|
||||
c-0.419-0.47-1.007-0.729-1.704-0.729c-0.521,0-0.967,0.113-1.315,0.351c-0.375,0.238-0.686,0.567-0.891,0.939
|
||||
C233.333,235.969,233.211,236.395,233.166,236.838L233.166,236.838z M242.83,236.648v5.206h-1.337v-8.651h1.293v1.361h0.089
|
||||
c0.213-0.454,0.516-0.805,0.939-1.086c0.397-0.265,0.945-0.395,1.596-0.395c0.87,0,1.596,0.275,2.136,0.816
|
||||
c0.535,0.518,0.793,1.365,0.793,2.462v5.492h-1.333v-5.4c0-0.702-0.162-1.215-0.516-1.587c-0.354-0.4-0.839-0.589-1.453-0.589
|
||||
c-0.634,0-1.177,0.21-1.593,0.637C243.013,235.314,242.83,235.903,242.83,236.648L242.83,236.648z M254.303,242.044
|
||||
c-0.821,0-1.526-0.189-2.117-0.578c-0.556-0.384-1.023-0.896-1.334-1.577c-0.327-0.68-0.467-1.452-0.467-2.301
|
||||
c0-0.891,0.14-1.663,0.467-2.349c0.333-0.675,0.802-1.199,1.383-1.566c0.588-0.41,1.269-0.589,2.049-0.589
|
||||
c0.607,0,1.14,0.119,1.636,0.352c0.492,0.205,0.889,0.535,1.199,0.935c0.324,0.399,0.519,0.896,0.581,1.437h-1.333
|
||||
c-0.089-0.405-0.327-0.751-0.659-1.064c-0.346-0.291-0.815-0.464-1.404-0.464c-0.778,0-1.412,0.308-1.874,0.891
|
||||
c-0.494,0.583-0.729,1.388-0.729,2.371c0,0.982,0.235,1.798,0.708,2.392c0.464,0.616,1.096,0.913,1.896,0.913
|
||||
c0.513,0,0.964-0.135,1.336-0.394c0.378-0.287,0.613-0.638,0.727-1.129h1.333c-0.062,0.518-0.256,0.988-0.556,1.388
|
||||
c-0.283,0.421-0.688,0.756-1.15,0.978C255.499,241.925,254.935,242.044,254.303,242.044L254.303,242.044z M259.652,241.854v-8.651
|
||||
h1.312v8.651H259.652z M260.33,231.767c-0.281,0-0.494-0.092-0.678-0.28c-0.184-0.162-0.284-0.379-0.284-0.633
|
||||
c0-0.253,0.1-0.469,0.284-0.637c0.184-0.184,0.397-0.248,0.678-0.248c0.256,0,0.472,0.064,0.664,0.248
|
||||
c0.184,0.168,0.275,0.384,0.275,0.637c0,0.254-0.092,0.471-0.275,0.633C260.802,231.675,260.586,231.767,260.33,231.767
|
||||
L260.33,231.767z M266.923,242.044c-0.775,0-1.453-0.189-2.039-0.557c-0.591-0.389-1.059-0.896-1.385-1.556
|
||||
c-0.332-0.675-0.492-1.457-0.492-2.344c0-0.912,0.16-1.685,0.492-2.37c0.327-0.681,0.794-1.199,1.385-1.577
|
||||
c0.586-0.378,1.264-0.557,2.039-0.557c0.772,0,1.477,0.179,2.065,0.557c0.589,0.378,1.029,0.896,1.364,1.577
|
||||
c0.324,0.686,0.491,1.458,0.491,2.37c0,0.887-0.167,1.669-0.491,2.344c-0.335,0.659-0.775,1.167-1.364,1.556
|
||||
C268.4,241.854,267.695,242.044,266.923,242.044L266.923,242.044z M266.923,240.845c0.588,0,1.077-0.156,1.477-0.438
|
||||
c0.375-0.313,0.651-0.729,0.848-1.226c0.187-0.491,0.284-1.026,0.284-1.594c0-0.588-0.097-1.134-0.284-1.619
|
||||
c-0.197-0.519-0.473-0.913-0.848-1.227c-0.4-0.291-0.889-0.464-1.477-0.464c-0.586,0-1.075,0.173-1.453,0.464
|
||||
c-0.408,0.313-0.683,0.708-0.875,1.227c-0.187,0.485-0.256,1.031-0.256,1.619c0,0.567,0.07,1.103,0.256,1.594
|
||||
c0.192,0.497,0.467,0.912,0.875,1.226C265.849,240.688,266.337,240.845,266.923,240.845L266.923,240.845z M266.313,232.031
|
||||
l1.382-2.635h1.553l-1.763,2.635H266.313z M274.222,236.648v5.206h-1.342v-8.651h1.288v1.361h0.122
|
||||
c0.189-0.454,0.491-0.805,0.918-1.086c0.424-0.265,0.937-0.395,1.596-0.395c0.889,0,1.593,0.275,2.128,0.816
|
||||
c0.542,0.518,0.805,1.365,0.805,2.462v5.492h-1.318v-5.4c0-0.702-0.192-1.215-0.54-1.587c-0.356-0.4-0.848-0.589-1.453-0.589
|
||||
c-0.629,0-1.172,0.21-1.569,0.637C274.427,235.314,274.222,235.903,274.222,236.648L274.222,236.648z M287.45,245.214
|
||||
c-0.208,0-0.416,0-0.605-0.049c-0.17-0.021-0.283-0.07-0.359-0.092l0.329-1.177c0.492,0.118,0.894,0.118,1.223-0.027
|
||||
c0.327-0.113,0.608-0.508,0.84-1.145l0.284-0.821l-3.191-8.7h1.431l2.398,6.891h0.089l2.4-6.891h1.423l-3.702,10.066
|
||||
C289.51,244.576,288.671,245.214,287.45,245.214L287.45,245.214z M303.8,233.203v1.123h-1.917v7.528h-1.342v-7.528h-1.401v-1.123
|
||||
h1.401v-1.199c0-0.491,0.122-0.912,0.351-1.242c0.238-0.329,0.516-0.566,0.894-0.729c0.375-0.189,0.775-0.265,1.196-0.265
|
||||
c0.33,0,0.586,0.032,0.797,0.075c0.216,0.076,0.356,0.125,0.467,0.162l-0.392,1.156c-0.076-0.032-0.173-0.07-0.287-0.098
|
||||
c-0.119-0.021-0.256-0.049-0.472-0.049c-0.416,0-0.748,0.092-0.935,0.33c-0.189,0.216-0.278,0.54-0.278,0.988v0.869H303.8z
|
||||
M308.854,242.044c-0.802,0-1.488-0.189-2.071-0.557c-0.589-0.389-1.053-0.896-1.388-1.556c-0.324-0.675-0.486-1.457-0.486-2.344
|
||||
c0-0.912,0.162-1.685,0.486-2.37c0.335-0.681,0.799-1.199,1.388-1.577c0.583-0.378,1.269-0.557,2.071-0.557
|
||||
c0.767,0,1.445,0.179,2.031,0.557s1.056,0.896,1.385,1.577c0.334,0.686,0.497,1.458,0.497,2.37c0,0.887-0.162,1.669-0.497,2.344
|
||||
c-0.33,0.659-0.799,1.167-1.385,1.556C310.299,241.854,309.622,242.044,308.854,242.044L308.854,242.044z M308.854,240.845
|
||||
c0.575,0,1.072-0.156,1.445-0.438c0.378-0.313,0.653-0.729,0.842-1.226c0.194-0.491,0.286-1.026,0.286-1.594
|
||||
c0-0.588-0.092-1.134-0.286-1.619c-0.189-0.519-0.464-0.913-0.842-1.227c-0.373-0.291-0.87-0.464-1.445-0.464
|
||||
c-0.624,0-1.11,0.173-1.488,0.464c-0.37,0.313-0.659,0.708-0.84,1.227c-0.194,0.485-0.283,1.031-0.283,1.619
|
||||
c0,0.567,0.089,1.103,0.283,1.594c0.181,0.497,0.47,0.912,0.84,1.226C307.745,240.688,308.231,240.845,308.854,240.845
|
||||
L308.854,240.845z M314.782,241.854v-8.651h1.285v1.307h0.1c0.165-0.416,0.443-0.767,0.867-1.053
|
||||
c0.396-0.254,0.864-0.395,1.385-0.395c0.095,0,0.211,0,0.375,0c0.135,0.021,0.262,0.021,0.33,0.021v1.367
|
||||
c-0.049-0.033-0.143-0.033-0.305-0.06c-0.167-0.021-0.33-0.044-0.516-0.044c-0.632,0-1.15,0.189-1.55,0.589
|
||||
c-0.421,0.378-0.634,0.87-0.634,1.458v5.46H314.782z M320.689,241.854v-8.651h1.299v1.361h0.113c0.195-0.471,0.473-0.821,0.87-1.086
|
||||
s0.891-0.395,1.445-0.395c0.569,0,1.063,0.13,1.439,0.395s0.678,0.615,0.891,1.086h0.1c0.208-0.454,0.532-0.821,0.977-1.086
|
||||
c0.448-0.265,0.988-0.395,1.604-0.395c0.767,0,1.404,0.249,1.896,0.734c0.494,0.481,0.746,1.237,0.746,2.242v5.794h-1.334v-5.794
|
||||
c0-0.633-0.165-1.102-0.519-1.361c-0.351-0.281-0.767-0.421-1.239-0.421c-0.61,0-1.083,0.188-1.41,0.562
|
||||
c-0.33,0.356-0.513,0.826-0.513,1.383v5.632h-1.336v-5.93c0-0.497-0.165-0.896-0.491-1.183c-0.308-0.291-0.735-0.464-1.223-0.464
|
||||
c-0.356,0-0.681,0.092-0.988,0.286c-0.278,0.178-0.538,0.442-0.702,0.75c-0.189,0.33-0.286,0.702-0.286,1.14v5.4H320.689z
|
||||
M337.044,242.065c-0.537,0-1.031-0.119-1.474-0.335c-0.454-0.173-0.821-0.48-1.083-0.886c-0.259-0.394-0.396-0.886-0.396-1.452
|
||||
c0-0.492,0.121-0.887,0.308-1.193c0.184-0.309,0.443-0.567,0.772-0.729c0.332-0.189,0.699-0.303,1.104-0.399
|
||||
c0.37-0.092,0.77-0.168,1.194-0.211c0.513-0.064,0.942-0.119,1.271-0.162c0.321-0.021,0.583-0.098,0.721-0.194
|
||||
c0.165-0.07,0.235-0.227,0.235-0.442v-0.044c0-0.566-0.165-0.988-0.47-1.29c-0.284-0.309-0.751-0.476-1.364-0.476
|
||||
c-0.629,0-1.129,0.141-1.499,0.427c-0.351,0.281-0.602,0.588-0.748,0.891l-1.272-0.442c0.238-0.54,0.516-0.945,0.918-1.247
|
||||
c0.378-0.281,0.772-0.486,1.223-0.616c0.464-0.108,0.886-0.179,1.331-0.179c0.286,0,0.594,0.038,0.967,0.092
|
||||
c0.351,0.065,0.702,0.217,1.05,0.422c0.332,0.194,0.616,0.496,0.851,0.912c0.232,0.405,0.329,0.962,0.329,1.643v5.702h-1.317v-1.166
|
||||
h-0.07c-0.094,0.178-0.229,0.394-0.448,0.604c-0.21,0.216-0.489,0.395-0.842,0.54C337.981,241.995,337.561,242.065,337.044,242.065
|
||||
L337.044,242.065z M337.255,240.866c0.518,0,0.961-0.092,1.339-0.297c0.348-0.216,0.632-0.476,0.823-0.799
|
||||
c0.187-0.335,0.278-0.687,0.278-1.037v-1.215c-0.07,0.069-0.189,0.113-0.373,0.161c-0.189,0.065-0.405,0.119-0.659,0.168
|
||||
c-0.232,0.016-0.472,0.064-0.705,0.086c-0.235,0.027-0.421,0.049-0.562,0.076c-0.354,0.043-0.686,0.113-0.958,0.211
|
||||
c-0.313,0.097-0.548,0.254-0.732,0.447c-0.184,0.217-0.281,0.471-0.281,0.795c0,0.469,0.162,0.82,0.51,1.058
|
||||
C336.294,240.753,336.712,240.866,337.255,240.866L337.255,240.866z M346.971,242.044c-0.799,0-1.507-0.189-2.084-0.578
|
||||
c-0.6-0.384-1.04-0.896-1.367-1.577c-0.302-0.68-0.467-1.452-0.467-2.301c0-0.891,0.165-1.663,0.491-2.349
|
||||
c0.332-0.675,0.775-1.199,1.361-1.566c0.589-0.41,1.267-0.589,2.039-0.589c0.613,0,1.172,0.119,1.666,0.352
|
||||
c0.473,0.205,0.873,0.535,1.194,0.935c0.308,0.399,0.5,0.896,0.591,1.437h-1.337c-0.097-0.405-0.324-0.751-0.678-1.064
|
||||
c-0.348-0.291-0.823-0.464-1.409-0.464c-0.772,0-1.413,0.308-1.879,0.891c-0.475,0.583-0.705,1.388-0.705,2.371
|
||||
c0,0.982,0.23,1.798,0.705,2.392c0.467,0.616,1.107,0.913,1.879,0.913c0.516,0,0.958-0.135,1.333-0.394
|
||||
c0.378-0.287,0.635-0.638,0.753-1.129h1.337c-0.092,0.518-0.284,0.988-0.564,1.388c-0.302,0.421-0.678,0.756-1.169,0.978
|
||||
C348.161,241.925,347.626,242.044,346.971,242.044L346.971,242.044z M352.317,241.854v-8.651h1.336v8.651H352.317z M352.995,231.767
|
||||
c-0.254,0-0.486-0.092-0.678-0.28c-0.165-0.162-0.262-0.379-0.262-0.633c0-0.253,0.097-0.469,0.262-0.637
|
||||
c0.192-0.184,0.424-0.248,0.678-0.248c0.259,0,0.5,0.064,0.659,0.248c0.195,0.168,0.287,0.384,0.287,0.637
|
||||
c0,0.254-0.092,0.471-0.287,0.633C353.494,231.675,353.254,231.767,352.995,231.767L352.995,231.767z M359.613,242.044
|
||||
c-0.799,0-1.482-0.189-2.06-0.557c-0.589-0.389-1.059-0.896-1.388-1.556c-0.329-0.675-0.491-1.457-0.491-2.344
|
||||
c0-0.912,0.162-1.685,0.491-2.37c0.33-0.681,0.799-1.199,1.388-1.577c0.578-0.378,1.261-0.557,2.06-0.557
|
||||
c0.778,0,1.453,0.179,2.042,0.557c0.591,0.378,1.056,0.896,1.38,1.577c0.334,0.686,0.497,1.458,0.497,2.37
|
||||
c0,0.887-0.162,1.669-0.497,2.344c-0.324,0.659-0.789,1.167-1.38,1.556C361.065,241.854,360.391,242.044,359.613,242.044
|
||||
L359.613,242.044z M359.613,240.845c0.591,0,1.08-0.156,1.453-0.438c0.405-0.313,0.681-0.729,0.845-1.226
|
||||
c0.192-0.491,0.284-1.026,0.284-1.594c0-0.588-0.092-1.134-0.284-1.619c-0.165-0.519-0.44-0.913-0.845-1.227
|
||||
c-0.373-0.291-0.861-0.464-1.453-0.464c-0.607,0-1.077,0.173-1.482,0.464c-0.37,0.313-0.651,0.708-0.837,1.227
|
||||
c-0.189,0.485-0.281,1.031-0.281,1.619c0,0.567,0.092,1.103,0.281,1.594c0.186,0.497,0.467,0.912,0.837,1.226
|
||||
C358.536,240.688,359.005,240.845,359.613,240.845L359.613,240.845z M359.005,232.031l1.385-2.635h1.544l-1.758,2.635H359.005z
|
||||
M366.887,236.648v5.206h-1.342v-8.651h1.293v1.361h0.119c0.213-0.454,0.513-0.805,0.937-1.086c0.399-0.265,0.937-0.395,1.596-0.395
|
||||
c0.869,0,1.571,0.275,2.114,0.816c0.535,0.518,0.818,1.365,0.818,2.462v5.492h-1.339v-5.4c0-0.702-0.157-1.215-0.535-1.587
|
||||
c-0.351-0.4-0.827-0.589-1.456-0.589c-0.637,0-1.147,0.21-1.577,0.637C367.101,235.314,366.887,235.903,366.887,236.648
|
||||
L366.887,236.648z M379.322,245.116v-11.913h1.291v1.383h0.162c0.094-0.168,0.208-0.356,0.402-0.589
|
||||
c0.156-0.237,0.418-0.442,0.745-0.638c0.33-0.184,0.775-0.275,1.339-0.275c0.731,0,1.385,0.179,1.922,0.557
|
||||
c0.567,0.356,1.007,0.869,1.313,1.556c0.302,0.659,0.472,1.452,0.472,2.37c0,0.908-0.17,1.713-0.472,2.365
|
||||
c-0.305,0.687-0.746,1.199-1.288,1.556c-0.562,0.367-1.199,0.557-1.917,0.557c-0.57,0-0.991-0.098-1.348-0.281
|
||||
c-0.324-0.188-0.583-0.399-0.767-0.632c-0.165-0.232-0.308-0.442-0.402-0.61h-0.116v4.596H379.322z M380.628,237.54
|
||||
c0,0.966,0.219,1.761,0.643,2.392c0.416,0.616,1.029,0.913,1.85,0.913c0.535,0,1.016-0.135,1.391-0.438
|
||||
c0.37-0.286,0.651-0.686,0.837-1.198c0.189-0.497,0.287-1.054,0.287-1.669c0-0.616-0.097-1.178-0.287-1.669
|
||||
c-0.186-0.491-0.467-0.864-0.815-1.145c-0.396-0.309-0.851-0.448-1.412-0.448c-0.821,0-1.434,0.308-1.85,0.891
|
||||
C380.847,235.785,380.628,236.557,380.628,237.54L380.628,237.54z M391.542,242.065c-0.542,0-1.061-0.119-1.504-0.335
|
||||
c-0.445-0.173-0.799-0.48-1.056-0.886c-0.259-0.394-0.397-0.886-0.397-1.452c0-0.492,0.097-0.887,0.305-1.193
|
||||
c0.186-0.309,0.445-0.567,0.77-0.729c0.33-0.189,0.678-0.303,1.088-0.399c0.4-0.092,0.794-0.168,1.191-0.211
|
||||
c0.54-0.064,0.961-0.119,1.288-0.162c0.33-0.021,0.57-0.098,0.732-0.194c0.143-0.07,0.208-0.227,0.208-0.442v-0.044
|
||||
c0-0.566-0.135-0.988-0.442-1.29c-0.308-0.309-0.751-0.476-1.361-0.476c-0.629,0-1.155,0.141-1.504,0.427
|
||||
c-0.351,0.281-0.61,0.588-0.748,0.891l-1.269-0.442c0.213-0.54,0.516-0.945,0.891-1.247c0.378-0.281,0.802-0.486,1.25-0.616
|
||||
c0.437-0.108,0.888-0.179,1.328-0.179c0.289,0,0.589,0.038,0.964,0.092c0.356,0.065,0.712,0.217,1.061,0.422
|
||||
c0.324,0.194,0.602,0.496,0.84,0.912c0.216,0.405,0.332,0.962,0.332,1.643v5.702h-1.342v-1.166h-0.04
|
||||
c-0.095,0.178-0.265,0.394-0.473,0.604c-0.208,0.216-0.489,0.395-0.824,0.54C392.479,241.995,392.059,242.065,391.542,242.065
|
||||
L391.542,242.065z M391.756,240.866c0.513,0,0.956-0.092,1.334-0.297c0.356-0.216,0.635-0.476,0.824-0.799
|
||||
c0.189-0.335,0.253-0.687,0.253-1.037v-1.215c-0.04,0.069-0.154,0.113-0.345,0.161c-0.189,0.065-0.427,0.119-0.656,0.168
|
||||
c-0.265,0.016-0.494,0.064-0.732,0.086c-0.216,0.027-0.397,0.049-0.562,0.076c-0.33,0.043-0.664,0.113-0.958,0.211
|
||||
c-0.286,0.097-0.551,0.254-0.732,0.447c-0.162,0.217-0.254,0.471-0.254,0.795c0,0.469,0.162,0.82,0.511,1.058
|
||||
C390.792,240.753,391.208,240.866,391.756,240.866L391.756,240.866z M397.945,241.854v-8.651h1.296v1.307h0.086
|
||||
c0.148-0.416,0.424-0.767,0.848-1.053c0.424-0.254,0.891-0.395,1.404-0.395c0.243,0,0.448,0.038,0.637,0.092
|
||||
c0.17,0.049,0.33,0.108,0.47,0.238l-0.448,1.117c-0.089-0.043-0.211-0.092-0.33-0.118c-0.138-0.021-0.278-0.044-0.438-0.044
|
||||
c-0.64,0-1.158,0.189-1.577,0.589c-0.395,0.378-0.608,0.87-0.608,1.458v5.46H397.945z M406.58,242.065
|
||||
c-0.538,0-1.029-0.119-1.475-0.335c-0.451-0.173-0.826-0.48-1.08-0.886c-0.262-0.394-0.402-0.886-0.402-1.452
|
||||
c0-0.492,0.119-0.887,0.305-1.193c0.192-0.309,0.448-0.567,0.772-0.729c0.33-0.189,0.708-0.303,1.102-0.399
|
||||
c0.378-0.092,0.802-0.168,1.202-0.211c0.51-0.064,0.937-0.119,1.267-0.162c0.329-0.021,0.583-0.098,0.726-0.194
|
||||
c0.165-0.07,0.235-0.227,0.235-0.442v-0.044c0-0.566-0.162-0.988-0.47-1.29c-0.281-0.309-0.753-0.476-1.361-0.476
|
||||
c-0.637,0-1.124,0.141-1.501,0.427c-0.348,0.281-0.61,0.588-0.753,0.891l-1.266-0.442c0.24-0.54,0.521-0.945,0.916-1.247
|
||||
c0.373-0.281,0.777-0.486,1.223-0.616c0.47-0.108,0.888-0.179,1.339-0.179c0.278,0,0.613,0.038,0.967,0.092
|
||||
c0.349,0.065,0.694,0.217,1.048,0.422c0.327,0.194,0.61,0.496,0.851,0.912c0.229,0.405,0.321,0.962,0.321,1.643v5.702h-1.313v-1.166
|
||||
h-0.07c-0.092,0.178-0.232,0.394-0.446,0.604c-0.213,0.216-0.494,0.395-0.845,0.54C407.514,241.995,407.099,242.065,406.58,242.065
|
||||
L406.58,242.065z M406.791,240.866c0.519,0,0.964-0.092,1.337-0.297c0.354-0.216,0.634-0.476,0.826-0.799
|
||||
c0.186-0.335,0.278-0.687,0.278-1.037v-1.215c-0.07,0.069-0.189,0.113-0.378,0.161c-0.181,0.065-0.397,0.119-0.651,0.168
|
||||
c-0.232,0.016-0.475,0.064-0.705,0.086c-0.235,0.027-0.424,0.049-0.564,0.076c-0.354,0.043-0.678,0.113-0.964,0.211
|
||||
c-0.302,0.097-0.535,0.254-0.726,0.447c-0.192,0.217-0.281,0.471-0.281,0.795c0,0.469,0.165,0.82,0.513,1.058
|
||||
C405.827,240.753,406.256,240.866,406.791,240.866L406.791,240.866z M418.809,230.309v11.546h-1.345v-11.546H418.809z
|
||||
M423.777,242.065c-0.54,0-1.061-0.119-1.506-0.335c-0.438-0.173-0.792-0.48-1.056-0.886c-0.253-0.394-0.394-0.886-0.394-1.452
|
||||
c0-0.492,0.097-0.887,0.308-1.193c0.184-0.309,0.44-0.567,0.77-0.729c0.329-0.189,0.683-0.303,1.077-0.399
|
||||
c0.399-0.092,0.802-0.168,1.196-0.211c0.545-0.064,0.961-0.119,1.296-0.162c0.324-0.021,0.559-0.098,0.727-0.194
|
||||
c0.146-0.07,0.213-0.227,0.213-0.442v-0.044c0-0.566-0.146-0.988-0.451-1.29c-0.294-0.309-0.748-0.476-1.358-0.476
|
||||
c-0.635,0-1.145,0.141-1.499,0.427c-0.383,0.281-0.613,0.588-0.777,0.891l-1.24-0.442c0.208-0.54,0.513-0.945,0.886-1.247
|
||||
c0.378-0.281,0.802-0.486,1.242-0.616c0.451-0.108,0.896-0.179,1.342-0.179c0.281,0,0.58,0.038,0.958,0.092
|
||||
c0.348,0.065,0.705,0.217,1.058,0.422c0.33,0.194,0.613,0.496,0.845,0.912c0.206,0.405,0.321,0.962,0.321,1.643v5.702h-1.328v-1.166
|
||||
h-0.049c-0.097,0.178-0.262,0.394-0.47,0.604c-0.208,0.216-0.497,0.395-0.821,0.54C424.717,241.995,424.271,242.065,423.777,242.065
|
||||
L423.777,242.065z M423.988,240.866c0.516,0,0.961-0.092,1.339-0.297c0.354-0.216,0.629-0.476,0.821-0.799
|
||||
c0.165-0.335,0.259-0.687,0.259-1.037v-1.215c-0.049,0.069-0.165,0.113-0.356,0.161c-0.192,0.065-0.413,0.119-0.659,0.168
|
||||
c-0.259,0.016-0.486,0.064-0.721,0.086c-0.24,0.027-0.429,0.049-0.564,0.076c-0.33,0.043-0.653,0.113-0.961,0.211
|
||||
c-0.313,0.097-0.537,0.254-0.724,0.447c-0.165,0.217-0.267,0.471-0.267,0.795c0,0.469,0.167,0.82,0.521,1.058
|
||||
C423.002,240.753,423.454,240.866,423.988,240.866L423.988,240.866z M434.641,241.854v-8.651h1.336v8.651H434.641z M435.321,231.767
|
||||
c-0.259,0-0.47-0.092-0.656-0.28c-0.194-0.162-0.286-0.379-0.286-0.633c0-0.253,0.092-0.469,0.286-0.637
|
||||
c0.187-0.184,0.397-0.248,0.656-0.248c0.256,0,0.492,0.064,0.678,0.248c0.192,0.168,0.284,0.384,0.284,0.637
|
||||
c0,0.254-0.092,0.471-0.284,0.633C435.813,231.675,435.577,231.767,435.321,231.767L435.321,231.767z M439.754,236.648v5.206h-1.342
|
||||
v-8.651h1.291v1.361h0.122c0.187-0.454,0.492-0.805,0.916-1.086c0.421-0.265,0.937-0.395,1.593-0.395
|
||||
c0.896,0,1.596,0.275,2.136,0.816c0.535,0.518,0.799,1.365,0.799,2.462v5.492h-1.313v-5.4c0-0.702-0.192-1.215-0.54-1.587
|
||||
c-0.356-0.4-0.851-0.589-1.461-0.589c-0.634,0-1.164,0.21-1.571,0.637C439.965,235.314,439.754,235.903,439.754,236.648
|
||||
L439.754,236.648z M451.228,242.044c-0.826,0-1.506-0.189-2.087-0.578c-0.589-0.384-1.062-0.896-1.364-1.577
|
||||
c-0.305-0.68-0.47-1.452-0.47-2.301c0-0.891,0.165-1.663,0.491-2.349c0.305-0.675,0.786-1.199,1.364-1.566
|
||||
c0.586-0.41,1.264-0.589,2.039-0.589c0.613,0,1.15,0.119,1.642,0.352c0.492,0.205,0.891,0.535,1.199,0.935
|
||||
c0.321,0.399,0.516,0.896,0.588,1.437h-1.32c-0.116-0.405-0.327-0.751-0.678-1.064c-0.348-0.291-0.818-0.464-1.404-0.464
|
||||
c-0.772,0-1.412,0.308-1.874,0.891c-0.476,0.583-0.71,1.388-0.71,2.371c0,0.982,0.213,1.798,0.71,2.392
|
||||
c0.461,0.616,1.072,0.913,1.874,0.913c0.516,0,0.958-0.135,1.329-0.394c0.378-0.287,0.616-0.638,0.753-1.129h1.32
|
||||
c-0.073,0.518-0.267,0.988-0.54,1.388c-0.311,0.421-0.705,0.756-1.177,0.978C452.418,241.925,451.857,242.044,451.228,242.044
|
||||
L451.228,242.044z M457.911,230.309v11.546h-1.336v-11.546H457.911z M465.792,238.317v-5.114h1.339v8.651h-1.339v-1.447h-0.089
|
||||
c-0.192,0.411-0.521,0.794-0.945,1.102c-0.418,0.324-0.985,0.459-1.617,0.459c-0.815,0-1.499-0.253-2.02-0.794
|
||||
c-0.513-0.539-0.794-1.36-0.794-2.462v-5.509h1.342v5.417c0,0.637,0.184,1.128,0.535,1.501c0.356,0.373,0.794,0.567,1.361,0.567
|
||||
c0.327,0,0.656-0.07,1.013-0.26c0.345-0.167,0.632-0.427,0.869-0.777C465.682,239.301,465.792,238.857,465.792,238.317
|
||||
L465.792,238.317z"/>
|
||||
<path fill="#2A2A2B" d="M475.694,235.147l-1.202,0.324c-0.113-0.303-0.3-0.589-0.581-0.843c-0.289-0.259-0.699-0.399-1.291-0.399
|
||||
c-0.519,0-0.961,0.118-1.313,0.378c-0.354,0.232-0.543,0.54-0.543,0.907c0,0.341,0.138,0.589,0.378,0.777
|
||||
c0.24,0.211,0.604,0.356,1.128,0.503l1.285,0.297c1.526,0.378,2.298,1.177,2.298,2.344c0,0.523-0.14,0.945-0.418,1.339
|
||||
c-0.281,0.399-0.686,0.713-1.202,0.913c-0.516,0.237-1.104,0.356-1.76,0.356c-0.891,0-1.615-0.189-2.204-0.578
|
||||
c-0.586-0.384-0.934-0.945-1.099-1.674l1.266-0.329c0.235,0.944,0.916,1.403,2.015,1.403c0.615,0,1.104-0.135,1.479-0.394
|
||||
c0.354-0.26,0.538-0.584,0.538-0.967c0-0.605-0.421-1-1.286-1.21l-1.437-0.335c-0.793-0.184-1.38-0.491-1.733-0.891
|
||||
c-0.378-0.373-0.559-0.896-0.559-1.48c0-0.491,0.138-0.912,0.395-1.29c0.283-0.373,0.656-0.675,1.15-0.892
|
||||
c0.472-0.205,1.012-0.324,1.62-0.324c0.861,0,1.55,0.179,2.014,0.557C475.132,234.019,475.483,234.51,475.694,235.147
|
||||
L475.694,235.147z M477.854,241.854v-8.651h1.336v8.651H477.854z M478.535,231.767c-0.262,0-0.47-0.092-0.659-0.28
|
||||
c-0.192-0.162-0.284-0.379-0.284-0.633c0-0.253,0.092-0.469,0.284-0.637c0.189-0.184,0.397-0.248,0.659-0.248
|
||||
c0.254,0,0.492,0.064,0.681,0.248c0.186,0.168,0.286,0.384,0.286,0.637c0,0.254-0.1,0.471-0.286,0.633
|
||||
C479.026,231.675,478.789,231.767,478.535,231.767L478.535,231.767z M485.147,242.044c-0.796,0-1.474-0.189-2.068-0.557
|
||||
c-0.581-0.389-1.026-0.896-1.35-1.556c-0.335-0.675-0.505-1.457-0.505-2.344c0-0.912,0.17-1.685,0.505-2.37
|
||||
c0.324-0.681,0.77-1.199,1.35-1.577c0.594-0.378,1.272-0.557,2.068-0.557c0.778,0,1.456,0.179,2.044,0.557
|
||||
c0.608,0.378,1.056,0.896,1.38,1.577c0.327,0.686,0.494,1.458,0.494,2.37c0,0.887-0.167,1.669-0.494,2.344
|
||||
c-0.324,0.659-0.772,1.167-1.38,1.556C486.603,241.854,485.925,242.044,485.147,242.044L485.147,242.044z M485.147,240.845
|
||||
c0.589,0,1.083-0.156,1.456-0.438c0.399-0.313,0.678-0.729,0.867-1.226c0.165-0.491,0.262-1.026,0.262-1.594
|
||||
c0-0.588-0.097-1.134-0.262-1.619c-0.189-0.519-0.467-0.913-0.867-1.227c-0.373-0.291-0.867-0.464-1.456-0.464
|
||||
c-0.61,0-1.077,0.173-1.474,0.464c-0.381,0.313-0.662,0.708-0.848,1.227c-0.189,0.485-0.278,1.031-0.278,1.619
|
||||
c0,0.567,0.089,1.103,0.278,1.594c0.187,0.497,0.467,0.912,0.848,1.226C484.07,240.688,484.537,240.845,485.147,240.845
|
||||
L485.147,240.845z M484.537,232.031l1.388-2.635h1.544l-1.755,2.635H484.537z M492.425,236.648v5.206h-1.315v-8.651h1.288v1.361
|
||||
h0.095c0.213-0.454,0.516-0.805,0.937-1.086c0.402-0.265,0.939-0.395,1.596-0.395c0.867,0,1.59,0.275,2.117,0.816
|
||||
c0.535,0.518,0.818,1.365,0.818,2.462v5.492h-1.345v-5.4c0-0.702-0.162-1.215-0.508-1.587c-0.356-0.4-0.848-0.589-1.455-0.589
|
||||
c-0.637,0-1.172,0.21-1.599,0.637C492.63,235.314,492.425,235.903,492.425,236.648L492.425,236.648z"/>
|
||||
<path fill="#163F75" d="M250.517,155.119c3.775,0,3.982-3.133,3.982-6.216v-30.609c0-3.148-0.208-6.318-3.982-6.318
|
||||
c-3.402,0-4.35,2.98-4.35,5.983v7.484c0,3.641-0.205,7.162-0.626,10.58c-0.429,3.424-1.059,6.27-1.971,8.543
|
||||
c-1.499,3.872-3.402,5.768-5.751,5.768c-2.552,0-3.818-3.71-3.818-11.092c0-7.037,0.259-13.35,0.753-18.961
|
||||
c0.235-2.273,0.354-4.077,0.354-5.417c0-1.971-1.085-2.959-3.243-2.959c-1.201,0-2,0.454-2.459,1.313
|
||||
c-1.296,2.44-1.95,10.893-1.95,25.322c0,11.26,3.005,16.579,9.194,16.579c3.259,0,5.983-1.663,8.098-4.995
|
||||
c0.721-1.103,1.142-1.686,1.261-1.686c0.094,0,0.159,0.238,0.159,0.745C246.167,152.192,247.115,155.119,250.517,155.119
|
||||
L250.517,155.119z M287.159,155.119c1.169,0,1.971-0.427,2.443-1.285c1.24-2.484,1.896-10.903,1.896-25.307
|
||||
c0-3.435-0.259-6.275-0.772-8.619c-1.177-5.352-3.964-8.003-8.395-8.003c-3.664,0-6.572,1.928-8.708,5.772
|
||||
c-0.281,0.551-0.521,0.778-0.659,0.778c-0.138,0-0.216-0.205-0.216-0.616c0-3.958-1.426-5.935-4.334-5.935
|
||||
c-1.55,0-2.625,0.523-3.194,1.464c-0.532,0.912-0.796,2.506-0.796,4.779v30.598c0,1.054,0.07,2.063,0.21,3.02
|
||||
c0.335,2.209,1.599,3.289,3.78,3.289c1.855,0,3.122-0.896,3.797-2.663c0.356-0.95,0.537-2.058,0.537-3.348v-7.928
|
||||
c0-8.182,0.875-14.382,2.582-18.681c1.555-3.823,3.478-5.746,5.805-5.746c1.677,0,2.86,1.647,3.394,4.839
|
||||
c0.333,1.923,0.476,4.315,0.476,7.178c-0.021,6.054-0.284,12.064-0.826,18.063c-0.232,2.274-0.354,4.078-0.354,5.4
|
||||
c0,1.988,1.11,2.949,3.292,2.949H287.159z M316.507,128.123c0,2.808-0.21,5.719-0.634,8.748c-0.397,3.003-1.059,5.557-1.874,7.599
|
||||
c-1.526,3.823-3.429,5.724-5.725,5.724c-2.935,0.141-3.85-6.475-3.85-13.512c0-4.784,1.439-19.679,8.284-19.679
|
||||
c1.855,0,3.027,0.47,3.47,1.404c0.237,0.476,0.329,1.01,0.329,1.577V128.123z M324.837,105.268c0-3.094-0.146-6.264-3.964-6.264
|
||||
c-3.362,0-4.366,2.916-4.366,5.983v7.177c0,0.54-0.064,0.848-0.235,0.848c-0.186,0-0.473-0.118-0.842-0.308
|
||||
c-1.056-0.54-2.185-0.799-3.381-0.799c-4.525,0-8.065,2.608-10.606,7.793c-2.392,4.931-3.585,11.373-3.585,19.284
|
||||
c0,8.517,3.121,16.212,9.221,16.072c3.284,0,5.979-1.696,8.139-5.05c0.656-1.031,1.01-1.566,1.123-1.566
|
||||
c0.103,0,0.167,0.206,0.167,0.605c0,3.072,1.004,6.011,4.366,6.011c3.818,0,3.964-3.176,3.964-6.309V105.268z M354.839,155.055
|
||||
c3.851,0.064,3.918-3.197,3.988-6.309v-30.565c-0.07-3.035-0.138-6.324-3.988-6.275c-1.307,0-2.317,0.405-2.935,1.198
|
||||
c-0.205,0.266-0.467,0.405-0.796,0.405c-0.229,0-0.583-0.097-0.932-0.329c-1.434-0.82-2.841-1.274-4.229-1.274
|
||||
c-4.528,0-8.047,2.608-10.601,7.793c-2.39,4.931-3.588,11.373-3.588,19.284c0,8.517,3.119,16.212,9.245,16.072
|
||||
c3.653,0,6.589-2.021,8.768-6.033c0.257-0.469,0.448-0.701,0.567-0.701c0.094,0,0.156,0.254,0.156,0.724
|
||||
C350.494,152.116,351.507,155.055,354.839,155.055L354.839,155.055z M342.175,150.193c-2.911,0.141-3.855-6.475-3.855-13.512
|
||||
c0-4.784,1.437-19.679,8.284-19.679c2.605,0,3.891,0.935,3.891,2.813v9.947c0,1.831-0.2,4.197-0.626,7.107
|
||||
c-0.445,2.938-1.083,5.486-1.971,7.599C346.396,148.293,344.495,150.193,342.175,150.193L342.175,150.193z M379.405,154.979
|
||||
c2.762,0,5.02-0.75,6.802-2.252c1.785-1.507,3.024-3.461,3.64-5.881c0.114-0.324,0.138-0.572,0.138-0.751
|
||||
c0-1.128-0.827-1.804-1.807-1.804c-0.821,0-1.434,0.416-1.809,1.329c-1.191,2.954-2.765,4.434-4.736,4.434
|
||||
c-2.563,0-4.458-1.853-5.754-5.558c-0.843-2.419-1.264-5.146-1.264-8.273c0-7.544,1.013-12.961,3.027-16.266
|
||||
c1.107-1.83,2.328-2.749,3.683-2.749c1.785,0,2.954,1.199,3.567,3.575c0.332,1.404,0.519,3.208,0.519,5.39
|
||||
c0,0.988-0.068,1.923-0.262,2.911c-0.064,0.442-0.094,1.145-0.094,2.014c0,1.453,0.17,2.814,1.906,2.814c1.993,0,3-2.744,3-8.188
|
||||
c0-9.191-3.151-13.793-9.378-13.793c-3.265,0-6.081,1.221-8.408,3.662c-4.01,4.223-6.027,11.098-6.027,20.629
|
||||
c0,3.791,0.47,6.999,1.361,9.607c0.985,2.847,2.533,5.114,4.623,6.832C374.035,154.212,376.465,154.979,379.405,154.979
|
||||
L379.405,154.979z M401.434,109.47c1.566,0,2.724-0.562,3.464-1.69c0.502-0.756,0.761-1.637,0.761-2.7
|
||||
c0-1.14-0.378-2.225-1.158-3.181c-0.748-0.988-1.761-1.485-3-1.485c-2.465,0-4.174,2.111-4.174,5.136
|
||||
C397.327,108.179,398.69,109.47,401.434,109.47L401.434,109.47z M401.263,155.055c2.917,0,4.396-2.021,4.396-6.011v-30.863
|
||||
c0-4.649-1.083-6.275-3.969-6.275c-2.881,0-4.363,2.095-4.363,5.989v30.852c0,1.054,0.067,2.063,0.216,3.02
|
||||
C397.861,153.975,399.084,155.055,401.263,155.055L401.263,155.055z M429.821,149.843c-2.89,0-5.047-1.712-6.475-5.093
|
||||
c-1.245-2.997-1.855-7.053-1.855-12.215c0-10.531,3.024-15.846,9.051-15.846c2.778,0,4.763,1.739,6.032,5.195
|
||||
c0.958,2.668,1.428,6.017,1.428,10.083c0,4.903-0.61,8.965-1.801,12.221C434.792,147.937,432.653,149.843,429.821,149.843
|
||||
L429.821,149.843z M427.542,154.979c5.582,0,9.737-2.203,12.386-6.583c2.301-3.926,3.451-9.288,3.451-16.099
|
||||
c0-5.368-0.821-9.813-2.527-13.252c-2.328-4.736-5.986-7.14-11.009-7.14c-5.773,0-10.088,2.214-12.926,6.643
|
||||
c-2.511,3.845-3.775,9.104-3.775,15.764c0,5.659,1.004,10.255,3.019,13.819C418.728,152.705,422.527,154.979,427.542,154.979
|
||||
L427.542,154.979z M425.924,108.67c1.288,0,3.213-0.799,5.773-2.349c2.549-1.545,3.869-3.116,3.869-4.661
|
||||
c0-1.339-0.659-1.998-1.974-1.998c-1.239,0-3.051,1.129-5.395,3.402c-2.322,2.23-3.47,3.732-3.47,4.574
|
||||
C424.728,108.319,425.122,108.67,425.924,108.67L425.924,108.67z M472.821,155.119c1.19,0,1.968-0.427,2.441-1.285
|
||||
c1.239-2.484,1.898-10.903,1.898-25.307c0-3.435-0.259-6.275-0.756-8.619c-1.188-5.352-3.98-8.003-8.419-8.003
|
||||
c-3.662,0-6.545,1.928-8.703,5.772c-0.278,0.551-0.491,0.778-0.629,0.778c-0.143,0-0.213-0.205-0.213-0.616
|
||||
c0-3.958-1.458-5.935-4.364-5.935c-1.525,0-2.608,0.523-3.188,1.464c-0.545,0.912-0.799,2.506-0.799,4.779v30.598
|
||||
c0,1.054,0.097,2.063,0.232,3.02c0.308,2.209,1.574,3.289,3.756,3.289c1.879,0,3.116-0.896,3.829-2.663
|
||||
c0.343-0.95,0.535-2.058,0.535-3.348v-7.928c0-8.182,0.842-14.382,2.56-18.681c1.566-3.823,3.473-5.746,5.79-5.746
|
||||
c1.717,0,2.886,1.647,3.429,4.839c0.327,1.923,0.464,4.315,0.464,7.178c-0.046,6.054-0.278,12.064-0.84,18.063
|
||||
c-0.235,2.274-0.332,4.078-0.332,5.4c0,1.988,1.085,2.949,3.281,2.949H472.821z"/>
|
||||
<g>
|
||||
<path fill="#163F75" d="M206.898,156.113c3.626,0,4.701-3.09,4.701-6.249v-28.687c0-1.08,0.634-1.625,1.812-1.625l7.788,0.092
|
||||
c1.807,0.092,2.806-0.908,2.806-2.085c0-1.086-0.362-1.814-1.094-2.263c-0.632-0.362-1.537-0.546-2.892-0.546
|
||||
c-2.079,0-4.158,0.184-6.237,0.184c-1.456,0-2.182-0.453-2.182-2.711v-5.427c0-1.998,0.91-2.992,2.725-2.992
|
||||
c0.996,0,2.352,0,3.523,0.098c1.094,0,8.689,0,9.502,0c2.714,0,3.988-1.27,3.988-2.452c0-1.62-1.091-2.533-3.076-2.803
|
||||
c-0.913-0.184-1.636-0.184-2.362-0.184h-9.224h-8.238c-2.986,0-4.79,1.183-5.519,3.629c-0.27,1.172-0.448,2.532-0.448,4.158v43.252
|
||||
c0,1.091,0.086,2.176,0.27,3.17C203.099,154.936,204.46,156.113,206.898,156.113L206.898,156.113z"/>
|
||||
</g>
|
||||
<g>
|
||||
<defs>
|
||||
<rect id="SVGID_23_" x="-476.587" y="-28.438" width="597.06" height="844.021"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_2_">
|
||||
<use xlink:href="#SVGID_23_" overflow="visible"/>
|
||||
</clipPath>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 40 KiB |
BIN
assets/fundacion/Versión Vertical_Isologotipo FS-26.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
assets/inq/MARCA FINAL 2023 AREA DE SEGURIDAD 1 BCO.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
assets/inq/MARCA FINAL 2023 AREA DE SEGURIDAD 1 COLOR.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
1
assets/inq/inq-color-svg.svg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
assets/re/.DS_Store
vendored
Normal file
BIN
assets/re/Paleta de colores RE-04.jpg
Normal file
|
After Width: | Height: | Size: 798 KiB |
1
assets/re/RE - SVG.svg
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
assets/re/Re-Monocromático.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
assets/re/Re-con fondo color secundario 1.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
assets/re/Re-con fondo color secundario 2.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
assets/re/Re-con fondo color secundario 3.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
assets/re/Re-fondo color primario.png
Normal file
|
After Width: | Height: | Size: 88 KiB |